P23-03: 生活の観察

This commit is contained in:
Agent Control Runner
2026-10-15 09:36:36 +09:00
parent 03100beabe
commit 4c8a18aa75
11 changed files with 2207 additions and 35 deletions
+828 -11
View File
@@ -10,9 +10,12 @@ import hashlib
import importlib.util
import json
import math
import os
from pathlib import Path
import re
import sys
import struct
import zlib
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parent.parent
@@ -24,15 +27,13 @@ CONDITIONS = (
'supplies_and_carried_goods', 'same_demand_route_improvement',
'normal_os_and_distinct_pid_resume',
)
BLOCKERS = {
CONDITIONS[0]: 'Service/rescue occupancy, reason fields and room rectangles checked; rest route selection and badge pixels unfinished.',
CONDITIONS[1]: 'Queue lane allocation, overflow and positions checked; screenshot pixel provenance unfinished.',
CONDITIONS[2]: 'Shaft/trip and cabin geometry checked; prior-frame interpolation history and passenger geometry unfinished.',
CONDITIONS[3]: 'Typed staff/task projection implemented; motion/rest/rescue/cleaning drawing recomputation unfinished.',
CONDITIONS[4]: 'Carried inventory ID and logistics report checked; supply badge pixels unfinished.',
CONDITIONS[5]: 'Public-command A/B fixture and typed arrival metric added; native execution, definition pins and full command/path provenance checks pending.',
CONDITIONS[6]: 'P23 passive request/ACK collector and independent CUA/formal-resume gates unfinished; Root evidence missing.',
}
CATEGORIES = ('room', 'queue', 'EV', 'staff', 'goods', 'business')
CONDITION_CHECKS = dict(zip(CONDITIONS, ('room_users', 'queue_overflow', 'elevator_passengers',
'staff_jobs', 'carried_goods', 'route_improvement', 'distinct_pid_resume')))
# Required names, not verdicts. BLOCKERS is computed from actual execution below.
REQUIRED_CHECKS = {condition: {category: {'semantic_' + category, 'pixels_' + category,
CONDITION_CHECKS[condition], 'os_guards', 'resume_recalculation'}
for category in CATEGORIES} for condition in CONDITIONS}
def module(name, path):
@@ -74,6 +75,199 @@ def sha(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def png_pixels(data):
"""Decode noninterlaced 8-bit RGB/RGBA PNG, validating the whole stream."""
require(data[:8] == b'\x89PNG\r\n\x1a\n', 'PNG signature')
pos, header, payload, ended, idat_closed = 8, None, bytearray(), False, False
while pos < len(data):
require(pos + 12 <= len(data), 'PNG truncated chunk')
size, kind = struct.unpack('>I4s', data[pos:pos + 8])
require(pos + size + 12 <= len(data), 'PNG truncated payload')
body = data[pos + 8:pos + 8 + size]
crc = struct.unpack('>I', data[pos + 8 + size:pos + 12 + size])[0]
require(zlib.crc32(kind + body) == crc, 'PNG CRC mismatch')
pos += size + 12
require(header is not None or kind == b'IHDR', 'PNG IHDR must be first')
if kind == b'IHDR':
require(header is None and size == 13, 'PNG duplicate/invalid IHDR')
w, h, depth, color, compression, filtering, interlace = struct.unpack('>IIBBBBB', body)
require(w > 0 and h > 0 and depth == 8 and color in (2, 6)
and (compression, filtering, interlace) == (0, 0, 0), 'PNG unsupported IHDR')
header = (w, h, 3 if color == 2 else 4)
elif kind == b'IDAT':
require(not idat_closed, 'PNG nonconsecutive IDAT')
payload.extend(body)
elif kind == b'IEND':
require(size == 0 and payload and pos == len(data), 'PNG invalid IEND')
ended = True
break
else:
require(kind[0] & 32 or kind == b'PLTE', 'PNG unknown critical chunk')
if payload:
idat_closed = True
require(ended and header is not None, 'PNG missing IEND')
w, h, channels = header
stride = w * channels
expected = h * (stride + 1)
try:
decoder = zlib.decompressobj()
raw = decoder.decompress(bytes(payload), expected + 1)
require(decoder.eof and not decoder.unused_data and not decoder.unconsumed_tail
and len(raw) == expected, 'PNG zlib length/end mismatch')
except zlib.error as exc:
raise ValueError('PNG invalid zlib') from exc
rows, prior = [], bytearray(stride)
for y in range(h):
start = y * (stride + 1)
mode = raw[start]
require(mode in range(5), 'PNG unknown filter')
row = bytearray(raw[start + 1:start + 1 + stride])
for x in range(stride):
left = row[x - channels] if x >= channels else 0
up = prior[x]
corner = prior[x - channels] if x >= channels else 0
if mode == 1:
predictor = left
elif mode == 2:
predictor = up
elif mode == 3:
predictor = (left + up) // 2
elif mode == 4:
p = left + up - corner
distances = (abs(p - left), abs(p - up), abs(p - corner))
predictor = (left, up, corner)[distances.index(min(distances))]
else:
predictor = 0
row[x] = (row[x] + predictor) & 255
rows.append([tuple(row[x:x + channels]) for x in range(0, stride, channels)])
prior = row
return w, h, rows
def image_kind(data):
if data.startswith(b'\x89PNG\r\n\x1a\n'):
w, h, _ = png_pixels(data)
return 'png', w, h
if data.startswith(b'\xff\xd8'):
helper = module('p23_p08_jpeg', ROOT / 'tools/check_p08_normal_placement_evidence.py')
w, h = helper.jpeg_dimensions(data)
return 'jpeg', w, h
raise ValueError('unknown screenshot byte magic')
def inspect_conversion(rec, cua_receipt, converter_sha256=None):
"""CUA receipt and converter pin are independent of conversion assertions."""
raw, png = Path(rec['raw_path']), Path(rec['png_path'])
require(raw.resolve() != png.resolve() and not raw.is_symlink() and not png.is_symlink(),
'conversion must preserve a separate raw file')
require(rec['raw_sha256'] == sha(raw) == cua_receipt['raw_sha256']
and rec['tool_call_id'] == cua_receipt['tool_call_id']
and bool(rec['tool_call_id']), 'CUA raw hash/call mismatch')
require(rec['raw_kind'] == 'jpeg', 'conversion raw kind')
kind, w, h = image_kind(raw.read_bytes())
require(kind == 'jpeg', 'conversion requires JPEG magic')
require(rec['converter'] == '/usr/bin/sips', 'conversion executable')
pin = converter_sha256 if converter_sha256 is not None else sha('/usr/bin/sips')
require(rec['converter_sha256'] == pin, 'converter SHA mismatch')
require(rec['argv'] == ['/usr/bin/sips', '-s', 'format', 'png', str(raw), '--out', str(png)],
'converter argv mismatch')
require(type(rec['exit_code']) is int and rec['exit_code'] == 0, 'converter exit code')
for key in ('stdout_sha256', 'stderr_sha256'):
require(isinstance(rec[key], str) and re.fullmatch('[0-9a-f]{64}', rec[key]),
'invalid converter output digest')
require(sha(png) == rec['png_sha256'], 'converted PNG SHA mismatch')
pw, ph, rows = png_pixels(png.read_bytes())
require((w, h) == (pw, ph) == (rec['width'], rec['height']), 'conversion dimensions mismatch')
return pw, ph, rows
def screenshot_pixels(path, conversion=None, cua_receipt=None, converter_sha256=None):
data = Path(path).read_bytes()
kind, _, _ = image_kind(data)
if kind == 'jpeg':
require(conversion is not None, 'JPEG screenshot requires conversion record')
require(Path(conversion['raw_path']).resolve() == Path(path).resolve(), 'conversion raw path mismatch')
require(cua_receipt is not None, 'missing CUA receipt')
return inspect_conversion(conversion, cua_receipt, converter_sha256)
return png_pixels(data)
def affine_point(matrix, point):
require(len(matrix) == 6 and all(type(v) in (int, float) and math.isfinite(v) for v in matrix), 'invalid affine')
a, b, c, d, x, y = matrix
require(a*d-b*c != 0, 'singular affine')
return [a*point[0]+c*point[1]+x, b*point[0]+d*point[1]+y]
def affine_rect(matrix, rect):
x, y, w, h = rect
points = [affine_point(matrix, p) for p in ([x,y], [x+w,y], [x,y+h], [x+w,y+h])]
lo = [min(p[i] for p in points) for i in (0, 1)]
return lo + [max(p[i] for p in points)-lo[i] for i in (0, 1)]
def compare_rectangles(report, audit, local_to_viewport, viewport_to_screen, local_to_screen, image, root=ROOT):
"""All draw rows, full affine composition, and exact existing style colors.
Font advance is recorded by the pinned engine/font, not approximated using
a new text width threshold. No image similarity threshold is introduced.
"""
for point in ([0, 0], [1, 0], [0, 1]):
close(affine_point(viewport_to_screen, affine_point(local_to_viewport, point)),
affine_point(local_to_screen, point), 'viewport/screen composition')
rows = audit['rows']
expected = []
tower, people, world = 'scripts/ui/pixel_tower_art.gd', 'scripts/ui/pixel_people.gd', 'scenes/main/world_preview.gd'
def actor(a, category=None):
expected.append((category or ('staff' if a['staff'] else 'guest'), a['key'],
a['at'] + [16*a['scale'], 18*a['scale']], people, None))
def badge(category, identifier, at, text):
expected.append((category, identifier, at + [None, 14], world, text))
for fid, room in report['rooms'].items():
if 'rect' in room:
expected.append(('room', fid, room['rect'], tower, None))
for i, b in enumerate(report['landing_badges']):
badge('queue', str(i), b['at'], b['text'])
for cabin in report['cabins']:
expected.append(('EV', cabin['shaft_id'], cabin['rect'], tower, None))
for a in cabin['passengers']:
actor(a)
for a in report['actor_drawings']:
actor(a)
if a['staff'] and a['key'].removeprefix('staff/') in report['resting_staff']:
actor(a, 'rest')
for i, b in enumerate(report['badges']):
badge('goods' if b['kind'] == 'supply' else 'business', str(i), b['at'], b['text'])
require(len(rows) == len(expected), 'rectangle coverage')
width, height, pixels = image
checked = []
for row, (category, identifier, rect, source, text) in zip(rows, expected):
require((row['category'], row['id'], row['style_source']) == (category, identifier, source), 'rectangle row identity')
if text is not None:
require(row['text'] == text and type(row['font_width']) in (int, float) and row['font_width'] > 0, 'badge font/text')
rect[2] = row['font_width'] + 6
close(row['rect'], rect, 'local drawing rectangle')
viewport = affine_rect(local_to_viewport, rect)
screen = affine_rect(viewport_to_screen, viewport)
close(row['viewport_rect'], viewport, 'viewport rectangle')
close(row['screen_rect'], screen, 'screen rectangle')
colors = list(dict.fromkeys(re.findall(r'Color\("([0-9a-fA-F]{6})"', (root/source).read_text())))
require(row['style_colors'] == colors and colors, 'drawing style palette')
x, y, w, h = screen
require(w > 0 and h > 0 and x >= 0 and y >= 0 and x+w <= width and y+h <= height,
'drawing rectangle outside screenshot')
# Badge ink is distinct from its translucent backing. Actor ink/skin
# and facility colors are taken verbatim from their pinned draw source.
palette = {'fff2ce'} if text is not None else set(colors)
palette -= {'303c40', 'c6d3b6', 'd5dec4'} # shadow / sky decorations
rgb = {tuple(bytes.fromhex(c)) for c in palette}
found = any(tuple(pixels[py][px][:3]) in rgb
for py in range(math.ceil(y), math.floor(y+h))
for px in range(math.ceil(x), math.floor(x+w)))
require(found, 'missing drawing pixels: ' + category + '/' + str(identifier))
checked.append(category + '/' + str(identifier))
return checked
def enum_names(root, relative, name):
source = (root / relative).read_text()
match = re.search(r'\benum\s+' + re.escape(name) + r'\s*\{([^}]+)\}', source)
@@ -322,6 +516,117 @@ def comparison_records(directory):
return records
def room_slots(rect, pixel, definition='staff_waiting_room'):
x, y, w, h = rect
step = 20 if definition in ('cafeteria', 'tavern') else 16
columns = max(int(w / pixel / step), 1)
rows = max(int((h / pixel - (30 if definition == 'chapel' else 10)) / 20), 1)
if definition == 'chapel':
columns = min(columns, 4)
anchors = [[x + (col + .5) * w / columns, y + h - (4 + row * 20) * pixel]
for row in range(rows) for col in range(columns)]
return anchors[:8] if definition == 'chapel' else anchors
def compare_rest(state, report, audit, root=ROOT):
"""Recompute selection, not shortest paths. Shortest-path authority is the
exact pinned PathService source producing the recorded public API paths.
Edge reports must be nonempty, including for temporarily closed cells.
"""
require(audit['path_service_sha256'] == sha(root / 'scripts/world/path_service.gd'),
'rest PathService source pin')
runtime = state['systems']['runtime']
members = runtime['staff']['staff']
facilities = runtime['facilities']['instances']
require([r['staff_id'] for r in audit['staff']] == [r['staff_id'] for r in members],
'rest staff order/coverage')
passable = {tuple(r['cell']) for r in runtime['path']['cell_traffic']}
resting = {}
selections = {}
for member, row in zip(members, audit['staff']):
sid = member['staff_id']
for key in ('state', 'health', 'current_task_id', 'current_cell'):
close(row[key], member[key], 'rest input/' + key)
memberships = [dict(raid_id=raid['raid_id'], state=raid['state'], side=c['side'])
for raid in runtime['defense']['raids'] for c in raid['combatants']
if c['actor_id'] == sid]
close(row['defense_memberships'], memberships, 'rest defense membership')
locked = sid in runtime['events']['actor_locks']
transit = any(t['actor_id'] == 'staff:' + sid for t in runtime['elevators']['trips'])
close(row['actor_lock'], locked, 'rest actor lock')
close(row['in_transit'], transit, 'rest transit')
defense = any(r['state'] not in ('DEFENDED', 'BREACHED', 'DEFENDERS_RETREATED', 'CANCELLED')
and r['side'] == 'DEFENDER' for r in memberships)
eligible = member['state'] == 'OFF_DUTY' and member['health'] > 0 and not member['current_task_id'] and not locked and not defense
selected = dict(reachable=False, resting=False, label='休憩先なし(稼働中の待機室・宿舎と経路を確認)') if eligible else {}
best_cost = 2147483647
require([f['instance_id'] for f in row['facilities']] == [f['instance_id'] for f in facilities],
'rest facility order/coverage')
for facility, observed in zip(facilities, row['facilities']):
for key in ('instance_id', 'definition_id', 'origin', 'occupied_cells'):
close(observed[key], facility[key], 'rest facility/' + key)
operation = 'SUSPENDED' if facility['suspension_reason'] else ('OPERATING' if facility['state'] in ('OPEN', 'DEGRADED') else 'INACTIVE')
close(observed['operation_state'], operation, 'rest operation')
occupied = facility['occupied_cells'] or [facility['origin']]
probes = [dict(cell=[c[0] + dx, c[1], c[2]], has_cell=(c[0] + dx, c[1], c[2]) in passable)
for c in occupied for dx in (0, -1, 1)]
close(observed['probes'], probes, 'rest candidate probes')
ordered = sorted({tuple(p['cell']) for p in probes if p['has_cell']}, key=lambda c: (c[2], c[1], c[0]))
close(observed['ordered'], [list(c) for c in ordered], 'rest candidate order')
require([r['cell'] for r in observed['routes']] == [list(c) for c in ordered], 'rest route coverage/order')
best = None
for route in observed['routes']:
path, edges = route['path'], route['edges']
require(len(edges) == max(len(path) - 1, 0), 'rest edge coverage')
if not path:
require(route['calculate_path_cost'] == -1, 'rest empty route cost')
continue
require(path[0] == member['current_cell'] and path[-1] == route['cell'], 'rest path endpoints')
require(all(tuple(c) in passable for c in path), 'rest path cell absent')
cost = 0
for a, b, edge in zip(path, path[1:], edges):
require(edge and edge['from'] == a and edge['to'] == b, 'rest empty or discontinuous edge report')
require(type(edge['total_cost']) is int and edge['total_cost'] >= 0, 'rest edge cost')
cost += edge['total_cost']
require(cost == route['calculate_path_cost'], 'rest edge sum differs from calculate_path_cost')
if best is None or cost < best[0]:
best = (cost, path[-1])
if not eligible or operation != 'OPERATING' or facility['definition_id'] not in ('staff_waiting_room', 'staff_dormitory') or best is None or best[0] >= best_cost:
continue
best_cost = best[0]
cell = member['current_cell']
at = any(c[2] == cell[2] and c[1] == cell[1] and abs(c[0] - cell[0]) <= 1 for c in occupied) and not transit
selected = dict(reachable=True, resting=at, cell=best[1], facility_id=facility['instance_id'],
label=('宿舎で休憩中' if facility['definition_id'] == 'staff_dormitory' else '待機室で休憩中') if at else '休憩先へ移動中')
close(row['comparison_only'], selected, 'rest selection comparison')
selections[sid] = selected
if selected.get('resting'):
resting[sid] = selected['facility_id']
close(report['resting_staff'], resting, 'draw resting_staff')
transform = Transform(runtime, report)
drawings = {r['key']: r for r in report['actor_drawings']}
for sid, fid in resting.items():
member = next(m for m in members if m['staff_id'] == sid)
if member['current_cell'][2] != report['layer'] or sid in report['carried_patients']:
continue
room = report['rooms'].get(fid, {})
cells = next((r['cells'] for r in runtime['world']['occupancies'] if r['entity_id'] == fid), [])
if not cells:
continue # renderer uses its ordinary tracked fallback
require(sid in room.get('people', []), 'rest room occupant missing')
slot = room['people'].index(sid)
anchors = room_slots(transform.room(cells), transform.dim[0] / 16)
key = 'staff/' + sid
if key in report['boarded'] or key in report['queue_hidden']:
require(key not in drawings, 'hidden resting drawing')
elif slot >= len(anchors):
require(key not in drawings, 'rest slot overflow drawing')
else:
close(drawings[key]['center'], [anchors[slot][0], anchors[slot][1] - transform.dim[1] * .5],
'resting staff position')
return selections
def compare_goods(snapshot, report, root=ROOT):
runtime = snapshot['systems']['runtime']
states = enum_names(root, 'scripts/inventory/logistics_request.gd', 'State')
@@ -356,6 +661,42 @@ def compare_goods(snapshot, report, root=ROOT):
close(report['goods'], dict(requests=rows, deliveries=active + [r[2] for r in recent[:10]]), 'goods')
def compare_badges(state, report):
runtime = state['systems']['runtime']
transform = Transform(runtime, report)
expected = []
labels = dict(BROKEN='破損', NO_ACCESS='通路なし', NO_POWER='魔力なし', NO_STAFF='職員不足',
CLOSED='営業停止', DEGRADED='低下運転', OPEN='営業中')
for facility in runtime['facilities']['instances']:
fid = facility['instance_id']
if facility['origin'][2] != report['layer']: continue
cells = next((r['cells'] for r in runtime['world']['occupancies'] if r['entity_id'] == fid), [])
if not cells: continue
x,y,w,h = transform.room(cells)
room = report['rooms'][fid]
count, limit = room['count'], facility['customer_capacity']
if facility['suspension_reason']:
business = '休止・摘発' if facility['suspension_reason'] == 'RAIDED' else '休止・破壊'
label = business
else:
business = labels.get(facility['state'], facility['state'])
if facility['state'] not in ('OPEN', 'DEGRADED'):
label = '未稼働'
else:
label = f'利用{count}/{limit}人' if limit >= 0 else f'利用{count}人'
if limit > 0 and count >= limit: label = '満員 ' + label
visible = len(room_slots([x,y,w,h], transform.dim[0]/16, facility['definition_id']))
if count > visible: label += f'(他{count-visible}人)'
expected.append(dict(facility=fid, kind='business', at=[x+2,y+2], text=business+' '+label))
requests = [r for r in report['goods']['requests'] if fid in (r['source_facility_id'], r['destination_facility_id'])]
if requests:
states = {}
for r in requests: states[r['state']] = states.get(r['state'], 0)+1
text = '物資依頼 ' + ' / '.join(f'{k}:{states[k]}' for k in sorted(states))
expected.append(dict(facility=fid, kind='supply', at=[x+2,y+16], requests=requests, text=text))
close(report['badges'], expected, 'business/supply badges')
def compare_cabin_geometry(snapshot, report):
runtime = snapshot['systems']['runtime']
transform = Transform(runtime, report)
@@ -394,6 +735,173 @@ def compare_cabin_geometry(snapshot, report):
close(row[key], value, 'cabin/' + key)
def compare_motion(state, report, audit):
frames = audit['frames']
require(len(frames) == 2, 'two motion history frames required')
previous, current = frames
require(type(previous['frame']) is int and current['frame'] > previous['frame'], 'motion frame order')
require(current['delta'] >= 0 and current['scaled_delta'] >= 0, 'motion delta')
before, after = previous['display'], current['display']
for key, value in after.items():
close(report[key], value, 'motion report binding/' + key)
close(after['animation_time'], before['animation_time'] + current['scaled_delta'], 'motion animation delta')
if before['paused'] and after['paused']:
close(current['scaled_delta'], 0, 'paused animation moved')
now = after['animation_time']
runtime = state['systems']['runtime']
cells = {'guest/' + r['guest_id']: r['current_cell'] for r in runtime['guests']['guests']}
cells.update({'staff/' + r['staff_id']: r['current_cell'] for r in runtime['staff']['staff']})
def interpolated(track, endpoint, paused, transform=None):
t = 1 if paused else max(0, min(1, (now-track['at'])/.42))
if transform:
a, b = transform.center(track['from']), transform.center(endpoint)
return [a[i] + (b[i]-a[i])*t for i in (0, 1)]
return track['from']+(endpoint-track['from'])*t
for key, track in after['actor_tracks'].items():
require(key in cells and track['cell'] == cells[key], 'motion actor endpoint')
old = before['actor_tracks'].get(key)
if old is None:
wanted = dict(cell=cells[key], **{'from': cells[key]}, at=now, direction=1.0)
elif old['cell'] == cells[key]:
wanted = old
else:
a, b = old['cell'], cells[key]
origin = a if a[2] == b[2] and abs(a[0]-b[0])+abs(a[1]-b[1]) == 1 else b
wanted = dict(cell=b, **{'from': origin}, at=now,
direction=(1.0 if b[0] > a[0] else -1.0) if a[0] != b[0] else old['direction'])
close(track, wanted, 'actor interpolation origin/history')
# Ordinary walking centers; queue/room/rescue/working projections have
# independent semantic placement checks and do not use this center.
drawing = next((a for a in after['actor_drawings'] if a['key'] == key), None)
if drawing and drawing['mode'] in ('walk', 'carry_walk') and key not in report['queue_positions']:
position = interpolated(track, cells[key], after['paused'], Transform(runtime, report))
# Same-cell crowd offsets are handled by the complete actor check.
require(all(math.isfinite(v) for v in position), 'nonfinite interpolation')
floors = {s['shaft_id']: s['visual_floor'] for s in elevator_report(state)['shafts']}
for key, track in after['elevator_tracks'].items():
require(key in floors, 'unknown elevator motion')
old = before['elevator_tracks'].get(key)
if old is None:
wanted = dict(floor=floors[key], **{'from': floors[key]}, at=now)
elif math.isclose(old['floor'], floors[key], abs_tol=.00001, rel_tol=.00001):
wanted = old
else:
wanted = dict(floor=floors[key], **{'from': old['floor']}, at=now)
close(track, wanted, 'elevator interpolation origin/history')
if before['paused'] and after['paused'] and all(before[k] == after[k] for k in ('layer', 'zoom', 'pan', 'viewport', 'actor_tracks', 'elevator_tracks')):
prior_actors = {r['key']: r for r in before['actor_drawings']}
for row in after['actor_drawings']:
old = prior_actors.get(row['key'])
if old and all(old[k] == row[k] for k in ('state', 'mode', 'activity', 'receiving', 'carrying')):
close(row['at'], old['at'], 'paused actor moved')
return ['motion_history', 'interpolation', 'pause']
def compare_actor_geometry(state, report):
runtime = state['systems']['runtime']
transform = Transform(runtime, report)
p, height = transform.dim[0]/16, transform.dim[1]
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
staff = codec.indexed(runtime['staff']['staff'], 'staff_id')
occupancy = {r['entity_id']: r['cells'] for r in runtime['world']['occupancies']}
counts, expected = {}, []
def track_position(key, cell):
track = report['actor_tracks'][key]
close(track['cell'], cell, 'actor authoritative cell')
t = 1 if report['paused'] else max(0, min(1, (report['animation_time']-track['at'])/.42))
a, b = transform.center(track['from']), transform.center(cell)
return [a[i]+(b[i]-a[i])*t for i in (0, 1)]
def offset(cell, center):
key = tuple(cell)
n = counts.get(key, 0)
counts[key] = n+1
return [center[0]+[0,4,-4,2,-2,6,-6][n%7]*p, center[1]-(n%2)*p]
def slot_position(fid, identifier, definition, fallback, dy, missing_fallback=False):
cells = occupancy.get(fid, [])
if not cells:
return fallback
users = report['rooms'].get(fid, {}).get('people', [])
if identifier not in users:
return fallback if missing_fallback else None
anchors = room_slots(transform.room(cells), p, definition)
index = users.index(identifier)
return [anchors[index][0], anchors[index][1]+dy] if index < len(anchors) else None
for guest in runtime['guests']['guests']:
key, cell = 'guest/'+guest['guest_id'], guest['current_cell']
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
continue
center = track_position(key, cell)
if key in report['queue_positions']:
center = report['queue_positions'][key]
elif guest['current_service_id']:
fid = guest['target_facility_id']
definition = facilities.get(fid, {}).get('definition_id', '')
cells = occupancy.get(fid, [])
if cells and cells[0][2] == report['layer']:
center = slot_position(fid, guest['guest_id'], definition, center,
3*p-height*.5-(5*p if definition in ('standard_room', 'premium_room') else 0), True)
else:
center = offset(cell, center)
if center is not None:
expected.append((key, center, False, bool(guest['current_service_id']), False, False))
for sid, member in staff.items():
key, cell = 'staff/'+sid, member['current_cell']
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
continue
center = track_position(key, cell)
special = True
if sid in report['carried_patients']:
carrier = staff.get(report['carried_patients'][sid])
if carrier:
center = track_position('staff/'+carrier['staff_id'], carrier['current_cell'])
center[1] -= 12*p
elif sid in report['resting_staff']:
center = slot_position(report['resting_staff'][sid], sid, 'staff_waiting_room', center, -height*.5)
elif sid in report['treated_patients']:
center = slot_position(report['treated_patients'][sid], sid, 'infirmary', center, -2*p-height*.5)
else:
special = False
center = offset(cell, center)
fid = member['assigned_facility_id']
cells = occupancy.get(fid, [])
workers = report['rooms'].get(fid, {}).get('staff', [])
if member['state'] in ('WORKING', 'AVAILABLE') and fid in facilities and cell in cells and sid in workers:
rect = transform.room(cells)
columns = max(int(rect[2]/(16*p)), 1)
index = workers.index(sid)
if rect[3] >= 40*p and index < columns:
center = [rect[0]+(index+.5)*rect[2]/columns, rect[1]+26*p-height*.5]
center = report['queue_positions'].get(key, center)
if center is not None:
expected.append((key, center, True, False, bool(member['carried_inventory_id']) and not special, False))
require([r['key'] for r in report['actor_drawings']] == [r[0] for r in expected], 'actor drawing coverage/order')
pairs = list(zip(report['actor_drawings'], expected))
shafts = {s['shaft_id']: s for s in elevator_report(state)['shafts']}
for cabin in report['cabins']:
shaft = shafts[cabin['shaft_id']]
x,y,w,h = cabin['rect']
columns = max(int((w/p-4)/8), 1)
passengers = shaft['boarded'][:columns*2]
require(len(cabin['passengers']) == len(passengers), 'passenger coverage')
for i, (actor_id, row) in enumerate(zip(passengers, cabin['passengers'])):
key = actor_id.replace('staff:', 'staff/', 1).replace('guest:', 'guest/', 1)
is_staff = key.startswith('staff/')
center = [x+(2+(i%columns+.5)*8)*p, y+h-(2+(i//columns)*9)*p+3*p-height*.5]
carried = bool(staff.get(key.removeprefix('staff/'), {}).get('carried_inventory_id')) if is_staff else False
pairs.append((row, (key, center, is_staff, False, carried, True)))
for row, (key, center, is_staff, receiving, carrying, riding) in pairs:
close(row['center'], center, 'actor center/interpolation')
scale = .5 if riding else .75
scale = max(math.floor(p*scale), 1) if p >= 2 else p*scale
close(row['scale'], scale, 'actor scale')
at = [center[0]-8*scale, center[1]+height*.5-3*p-17*scale]
rounded = [math.copysign(math.floor(abs(v)+.5), v) for v in at]
close(row['at'], rounded, 'actor drawing position')
require((row['key'], row['staff'], row['receiving'], row['carrying'], row['riding']) ==
(key, is_staff, receiving, carrying, riding), 'actor drawing attributes')
return ['actor_geometry', 'passenger_geometry']
def compare_layouts(a, b):
require(a['declaration'] == b['declaration'], 'identical predeclared inputs')
declaration = a['declaration']
@@ -452,30 +960,339 @@ def inspect_stream(directory, root=ROOT):
labels.add(row['label'])
require(type(row['pid']) is int and row['pid'] > 0, 'actual diagnostic PID required')
state = read_object(directory, row['state'])
close(read_object(directory, row['state_after']), state, 'read-only audit snapshot identity')
report = read_object(directory, row['report'])
fields = compare_actor_rows(state, report, root)
require(report['elevators'] == elevator_report(state), 'typed elevator report mismatch')
compare_room_services(state, report, root)
compare_rest(state, report, read_object(directory, row['rest_audit']), root)
compare_queues(state, report)
compare_traffic(state, report)
compare_goods(state, report, root)
compare_badges(state, report)
compare_cabin_geometry(state, report)
compare_motion(state, report, read_object(directory, row['motion']))
compare_actor_geometry(state, report)
checked.append(dict(label=row['label'], fields=fields + ['elevators', 'room_service_fields',
'queues', 'traffic', 'goods', 'cabin_geometry']))
require(checked, 'empty diagnostic stream')
return checked
def condition_matrix():
return [dict(condition=name, status='blocked', reason=BLOCKERS[name]) for name in CONDITIONS]
def condition_matrix(executed=None):
executed = executed or {}
result = []
for condition in CONDITIONS:
categories = {}
for category in CATEGORIES:
actual = set(executed.get(condition, {}).get(category, []))
required = REQUIRED_CHECKS[condition][category]
missing = sorted(required - actual)
categories[category] = dict(checks=sorted(actual), missing=missing,
status='passed' if actual and not missing else 'pending')
result.append(dict(condition=condition, categories=categories,
status='passed' if all(c['status'] == 'passed' for c in categories.values()) else 'pending'))
return result
def BLOCKERS(executed=None):
return {r['condition']: {c: d['missing'] for c, d in r['categories'].items() if d['missing']}
for r in condition_matrix(executed) if r['status'] != 'passed'}
CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'
PLAN = 'docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md'
def normative_plan_text(root):
"""Requirement text is an input; explicitly labelled progress is metadata."""
text = (root / PLAN).read_text()
section = re.search(r'^### P23[^\n]*\n(.*?)(?=^### |\Z)', text, re.M | re.S)
require(section is not None, 'missing normative #p23 plan section')
lines = []
for line in section.group(0).splitlines():
if line.startswith('現在地:'):
continue
line = line.split(' 状態:', 1)[0].rstrip()
if line:
lines.append(line)
require(any(line.startswith('- P23-03 ') and '受入:' in line for line in lines),
'missing P23-03 work/acceptance plan text')
return '\n'.join(lines) + '\n'
def input_map(root):
names = [SPEC, CONTRACT, 'AGENTS.md', 'docs/traceability/original/BASELINE.txt']
names += [p.relative_to(root).as_posix() for p in (root / 'tests/fixtures/p23-03').rglob('*')
if p.is_file()]
pins = {name: sha(root / name) for name in sorted(names)}
pins[PLAN + '#p23-normative'] = hashlib.sha256(normative_plan_text(root).encode()).hexdigest()
return pins
def input_provenance(root):
# Full-plan progress changes deliberately do not participate in input equality.
return {'full_plan_sha256': sha(root / PLAN)}
def inspect_os_receipt(receipt, source_map, pins=None):
require(receipt.get('engine_ended') is True, 'engine_ended must be true')
require(receipt.get('passed') is True and receipt.get('exit_code') == 0, 'unsuccessful OS process')
require(not receipt.get('error') and receipt.get('guard_errors') == []
and receipt.get('cleanup_errors') == [], 'OS cleanup/guard failure')
require(receipt.get('default_before') == receipt.get('default_after')
and isinstance(receipt.get('default_before'), dict), 'default profile changed')
require(receipt.get('stage_before') == receipt.get('stage_after')
and isinstance(receipt.get('stage_before'), dict), 'stage changed')
require(receipt.get('source_map') == source_map, 'OS source pin differs')
after = receipt.get('source_after', {})
require({name: pin['sha256'] for name, pin in after.items()} == source_map, 'source changed')
cleanup = receipt.get('owned_cleanup')
require(isinstance(cleanup, list) and cleanup
and all(row.get('engine_ended') is True for row in cleanup), 'missing owned cleanup')
require(bool(receipt.get('ordinary_owner_proof')), 'missing ordinary audio owner proof')
for field in ('engine_sha256', 'settings_sha256', 'environment_sha256'):
require(isinstance(receipt.get(field), str) and re.fullmatch('[0-9a-f]{64}', receipt[field])
and receipt[field] == receipt.get(field + '_after'), 'OS pin changed: ' + field)
require(receipt.get('spec_sha256') == SPEC_SHA, 'OS immutable spec')
require(receipt.get('inputs') and receipt['inputs'] == receipt.get('inputs_after'), 'OS inputs changed')
if pins is not None:
require(receipt['inputs'] == pins, 'OS input applicability')
require(receipt.get('environment') and receipt['environment'] == receipt.get('environment_after'), 'OS environment changed')
require(type(receipt.get('actual_pid')) is int and receipt['actual_pid'] > 0, 'OS actual PID')
require(receipt.get('profile') and receipt.get('profile_name') and isinstance(receipt.get('profile_after'), dict), 'OS private profile provenance')
before = receipt.get('profile_before', {})
require(type(before.get('existed')) is bool and isinstance(before.get('files'), dict), 'OS profile before')
for member, pin in before['files'].items():
require(receipt['profile_after'].get(member) == pin, 'OS profile input changed: ' + member)
def inspect_resume(writer, reader, saved, requests, formal_members):
require(type(writer['actual_pid']) is int and type(reader['actual_pid']) is int
and writer['actual_pid'] > 0 and reader['actual_pid'] > 0
and writer['actual_pid'] != reader['actual_pid'], 'resume requires distinct actual PIDs')
require(saved['pid'] == writer['actual_pid'], 'formal writer PID')
require(set(formal_members) == {'world.bin', 'manifest.json', 'thumbnail.png', 'journal.log'}, 'formal members coverage')
close(saved['members'], formal_members, 'formal member hashes')
require({k: v['sha256'] for k, v in reader['formal_input'].items()} == formal_members, 'reader formal input hashes')
loads = [row for row in requests if row['request']['action'] == 'load_menu']
require(len(loads) == 1, 'missing or duplicate normal load ACK')
item = loads[0]
request, ack, observed, cua = [item[k] for k in ('request', 'ack', 'observed', 'cua')]
require(request['target_pid'] == ack['target_pid'] == observed['pid'] == cua['target_pid'] == reader['actual_pid'], 'load ACK PID')
require(request['sequence'] == ack['sequence'] == observed['sequence'], 'load ACK request ID')
require(ack['request_sha256'] == item['request_sha256'] == observed['request_sha256'], 'load ACK hash')
require(0 < request['requested_ticks_usec'] < observed['ack_observed_ticks_usec'], 'load ACK time order')
require(cua['method'] == 'native_os' and cua['tool_call_id'] and cua['timestamp_ns'] > 0, 'load CUA provenance')
require(request['requested_unix_ns'] <= cua['timestamp_ns'] <= ack['timestamp_ns'] <= observed['ack_observed_unix_ns'], 'load external ACK time order')
require(observed['status'] == '手動セーブを読み込みました。', 'normal load result')
return ['distinct_pid', 'formal_members', 'load_ack']
def inspect_os_requests(directory, layout, role, pid, root=ROOT):
pair = directory/'os-pair'/layout
def path(name):
result = directory/name
require(result.resolve().is_relative_to(directory.resolve()) and not result.is_symlink(), 'OS artifact escape')
return result
def document(name):
return json.loads(path(name).read_bytes())
listing = json.loads((pair/(role+'-requests.json')).read_bytes())
require(listing['pid'] == pid and listing['requests'] and len(set(listing['requests'])) == len(listing['requests']), 'OS request list')
bindings = json.loads((pair/(role+'-screenshots.json')).read_bytes())
seen, records, pixel_categories = set(), [], set()
stream = pair/(role+'-stream')
captures = {row['label']: row for row in map(json.loads, (stream/'manifest.jsonl').read_text().splitlines()) if 'report' in row}
prior_tick = -1
for sequence, ack_name in enumerate(listing['requests'], 1):
ack_path = path(ack_name)
request_path = ack_path.with_name('request.json')
observed = json.loads(ack_path.with_name('observed.json').read_bytes())
request, ack = json.loads(request_path.read_bytes()), json.loads(ack_path.read_bytes())
cua = document(ack['input_receipt'])
require(request['sequence'] == ack['sequence'] == observed['sequence'] == sequence, 'OS request sequence')
require(request['target_pid'] == ack['target_pid'] == observed['pid'] == cua['target_pid'] == pid, 'OS request PID')
digest = sha(request_path)
require(ack['request_sha256'] == observed['request_sha256'] == digest, 'OS ACK hash')
require(prior_tick < request['requested_ticks_usec'] < observed['ack_observed_ticks_usec'], 'OS ACK order')
prior_tick = observed['ack_observed_ticks_usec']
require(request['requested_unix_ns'] <= cua['timestamp_ns'] <= ack['timestamp_ns'] <= observed['ack_observed_unix_ns'], 'OS external ACK time order')
require(cua['method'] == 'native_os' and cua['tool_name'] and cua['tool_call_id']
and cua['timestamp_ns'] > 0 and cua['collector_pid'] > 0 and cua['collector_pid'] != pid, 'OS CUA provenance')
require('arguments' in cua and isinstance(cua['arguments'], dict), 'full CUA arguments required')
if request['action'] != 'screenshot':
require(cua['events'] and observed['events'], 'OS input events missing')
if request['action'] == 'wheel':
require(request['ctrl'] is False and any(e.get('ctrl') is False and e.get('pressed') for e in observed['events']), 'ordinary wheel evidence')
report = decode(request['report'])
capture = captures[request_path.parent.name]
close(read_object(stream, capture['report']), report, 'request draw report binding')
for number, shot in enumerate(cua['screenshots']):
matches = [b for b in bindings if b['request'] == str(request_path.relative_to(directory)) and b['screenshot_index'] == number]
require(len(matches) == 1, 'screenshot binding coverage')
binding = matches[0]
identity = (binding['request'], number)
require(identity not in seen, 'duplicate screenshot binding')
seen.add(identity)
require(binding['request_sha256'] == digest and binding['target_pid'] == pid and binding['sequence'] == sequence,
'screenshot request attribution')
require(binding['input_receipt'] == ack['input_receipt'] and binding['input_receipt_sha256'] == sha(path(ack['input_receipt'])), 'screenshot CUA receipt hash')
require(binding['raw_path'] == shot['path'] and binding['raw_sha256'] == shot['raw_sha256'] == sha(path(shot['path']))
and binding['tool_call_id'] == shot['tool_call_id'] and shot['tool_call_id'], 'screenshot raw provenance')
conversion = document(binding['conversion']) if binding['conversion'] else None
image = screenshot_pixels(path(shot['path']), conversion, shot)
require(binding['png_sha256'] == sha(path(binding['png_path'])) and (binding['width'], binding['height']) == image[:2], 'decoded screenshot binding')
if conversion:
require(Path(conversion['png_path']).resolve() == path(binding['png_path']).resolve(), 'converted output binding')
for suffix in ('stdout', 'stderr'):
require(sha(path(binding['conversion']+'.'+suffix)) == conversion[suffix+'_sha256'], 'converter log hash')
else:
require(binding['png_path'] == binding['raw_path'], 'PNG must bind original bytes')
# Screenshot actions observe a stable report; input before/after
# screenshots are provenance only and never relabelled as this frame.
if request['action'] == 'screenshot':
checked = compare_rectangles(report, decode(request['rectangles']), request['local_to_viewport'],
request['viewport_to_screen'], request['local_to_screen'], image, root)
pixel_categories.update(c.split('/')[0] for c in checked)
records.append(dict(request=request, ack=ack, observed=observed, cua=cua, request_sha256=digest))
require(len(seen) == len(bindings), 'unattributed screenshot bindings')
return records, pixel_categories
def inspect_native_phases(directory, index, root=ROOT):
runner = module('p23_existing_runner_checks', root/'tools/run_all_tests.py')
expected = ['runs/'+name+'/'+name+'-receipt.json' for name in ('import', 'parse', 'layout-A', 'layout-B')]
require(index['phases'] == expected, 'native phase coverage/order')
for name in expected:
path = directory/name
receipt = json.loads(path.read_bytes())
pins = json.loads(path.with_name('p23-inputs.json').read_bytes())
require(pins['receipt_sha256'] == sha(path), 'native receipt pin')
require(pins['source_map'] == index['source_map'] and {k: v['sha256'] for k, v in pins['source_after'].items()} == index['source_map'], 'native sources changed')
require(pins['inputs'] == pins['inputs_after'] == input_map(root) and pins['spec_sha256'] == SPEC_SHA, 'native input applicability')
require(pins['default_before'] == pins['default_after'] and pins['stage_before'] == pins['stage_after'], 'native profile/stage changed')
require(pins['settings_sha256'] == sha(root/'project.godot'), 'native settings changed')
require(receipt['passed'] is True and receipt['failed'] is False and receipt['engine_ended'] is True
and receipt['exit_code'] == 0 and not receipt.get('validation_error') and not receipt.get('guard_error'), 'native process failed')
capture = receipt['outer_actual_capture']
require(capture['passed'] is True and capture['all_captured_ended'] is True, 'native owned cleanup')
require(receipt['whole_startup_profile_after']['fresh_before_engine'] is True, 'native profile protection')
require(receipt['default_player_entire_profile_unchanged'] is True, 'native default profile changed')
require(isinstance(receipt['controlled_environment'], dict), 'native environment absent')
diagnostics = receipt['finite_original_diagnostic_review']
require(runner.process_gate(receipt['native_process'], diagnostics['allowed_e31_error_lines']), 'native process gate')
if receipt['test']:
require(receipt['source_sha256'] == receipt['source_sha256_end'] == sha(root/receipt['test'])
and receipt['completion']['passed'] is True and receipt['reported_checks'] > 0 and receipt['reported_failures'] == 0, 'native strict marker')
def check(root, directory):
"""Never infer completion of unimplemented gates from an index assertion."""
deficits = []
executed = {c: {category: set() for category in CATEGORIES} for c in CONDITIONS}
semantic, pixel_categories, anchors = set(), set(), set()
index_path = directory / 'index.json'
if not index_path.is_file():
return ['missing evidence index: ' + str(index_path)]
try:
index = json.loads(index_path.read_bytes())
runner = module('p23_source_applicability', root/'tools/run_all_tests.py')
require(index.get('source_map') == {n: v['sha256'] for n, v in runner.source_refs(root).items()}, 'exact frozen source map')
if index.get('input_map') != input_map(root):
deficits.append('normative input_map differs from current immutable inputs')
if index.get('spec_sha256') != SPEC_SHA:
deficits.append('immutable spec SHA missing or different')
if index.get('marker_contract_sha256') != sha(root / CONTRACT):
deficits.append('marker contract SHA missing or different')
artifacts = index.get('artifacts', {})
if not artifacts:
deficits.append('missing indexed evidence artifacts')
for name, digest in artifacts.items():
path = directory / name
require(path.resolve().is_relative_to(directory.resolve()) and not path.is_symlink(),
'artifact escapes evidence directory')
require(path.is_file() and sha(path) == digest, 'artifact SHA mismatch: ' + name)
inspect_native_phases(directory, index, root)
for layout in ('A', 'B'):
layout_semantic, layout_pixels = set(), set()
stream = directory / ('layout-' + layout)
if (stream / 'manifest.jsonl').is_file():
inspect_stream(stream, root)
else:
deficits.append('missing native layout-' + layout + ' typed diagnostic stream')
for role in ('writer', 'reader'):
stream = directory / 'os-pair' / layout / (role + '-stream')
if (stream / 'manifest.jsonl').is_file():
inspect_stream(stream, root)
# Successful independent recalculation, with nonempty
# observed rows required before a category earns coverage.
for line in (stream/'manifest.jsonl').read_text().splitlines():
capture = json.loads(line)
if 'report' not in capture: continue
report = read_object(stream, capture['report'])
presence = {'room': report['rooms'], 'queue': report['landing_badges'],
'EV': report['cabins'], 'staff': report['staff'],
'goods': report['goods']['requests'], 'business': report['badges']}
semantic.update('semantic_'+c for c, rows in presence.items() if rows)
layout_semantic.update(c for c, rows in presence.items() if rows)
if any(r['people'] for r in report['rooms'].values()): anchors.add('room_users')
if any(r['overflow'] > 0 for r in report['landing_badges']): anchors.add('queue_overflow')
if any(r['passengers'] for r in report['cabins']): anchors.add('elevator_passengers')
if report['staff'] and report['tasks']: anchors.add('staff_jobs')
if any(r['carried_inventory_id'] for r in report['staff']) and report['goods']['requests']: anchors.add('carried_goods')
if role == 'reader': anchors.add('resume_recalculation')
else:
deficits.append('missing OS ' + layout + '-' + role + ' typed diagnostic stream')
ended = directory / 'os-runs' / (layout + '-' + role) / 'ended.json'
if ended.is_file():
receipt = json.loads(ended.read_bytes())
inspect_os_receipt(receipt, index.get('source_map'), input_map(root))
records, categories = inspect_os_requests(directory, layout, role, receipt['actual_pid'], root)
pixel_categories.update('pixels_'+c for c in categories if c in CATEGORIES)
layout_pixels.update(categories)
if role == 'reader':
writer = json.loads((directory/'os-runs'/(layout+'-writer')/'ended.json').read_bytes())
pair = directory/'os-pair'/layout
members = {p.name: sha(p) for p in (pair/'formal').iterdir() if p.is_file()}
inspect_resume(writer, receipt, json.loads((pair/'save.json').read_bytes()), records, members)
require((pair/'writer-formal-model.var').read_bytes() == (pair/'reader-formal-model.var').read_bytes(), 'resumed whole formal model')
anchors.add('distinct_pid_resume')
else:
deficits.append('missing OS ' + layout + '-' + role + ' process receipt')
for category in CATEGORIES:
if category not in layout_semantic or category not in layout_pixels:
deficits.append('missing actual OS ' + layout + ' semantic/pixel coverage: ' + category)
if all((directory / ('layout-' + layout) / 'manifest.jsonl').is_file() for layout in ('A', 'B')):
compare_layouts(comparison_records(directory / 'layout-A'), comparison_records(directory / 'layout-B'))
anchors.add('route_improvement')
if not deficits:
anchors.add('os_guards')
for condition in CONDITIONS:
for category in CATEGORIES:
executed[condition][category].update((semantic | pixel_categories | anchors) & REQUIRED_CHECKS[condition][category])
deficits.extend(condition + ': ' + json.dumps(missing, ensure_ascii=False) for condition, missing in BLOCKERS(executed).items())
except (OSError, ValueError, KeyError, TypeError, AttributeError) as exc:
deficits.append('invalid evidence index: ' + str(exc))
return deficits
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify-only', action='store_true')
parser.add_argument('--project-root', type=Path, default=ROOT)
parser.add_argument('--diagnostics', type=Path)
parser.add_argument('--layout-a', type=Path)
parser.add_argument('--layout-b', type=Path)
args = parser.parse_args(argv)
if args.verify_only:
build = os.environ.get('BUILD_OUTPUT_DIR')
if not build:
print('BUILD_OUTPUT_DIR is required', file=sys.stderr)
return 2
deficits = check(args.project_root, Path(build) / 'p23-life-observation-evidence')
print(json.dumps(dict(task_id='P23-03', integrity='fail' if deficits else 'pass',
deficits=deficits, acceptance='pending'), ensure_ascii=False))
return 1 if deficits else 0
# Registration hashes the immutable JSON after trimming outer whitespace.
require(hashlib.sha256((ROOT / SPEC).read_text().strip().encode()).hexdigest() == SPEC_SHA,
'immutable spec mismatch')
+343 -14
View File
@@ -1,25 +1,354 @@
#!/usr/bin/env python3
"""Read-only P23 readiness report. Collection is blocked in this candidate.
No runner, engine, profile, subprocess, browser or OS input is started. Root must
not mistake this preparation interface for a submitted native/OS collector.
"""
"""Root-only native collection. Does not run regression or claim acceptance."""
import argparse
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
import uuid
import re
sys.dont_write_bytecode = True
from check_p23_life_observation_evidence import condition_matrix
HERE = Path(__file__).resolve().parent
CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'
CONDITIONS = ('room_occupancy_and_operation', 'queues_and_overflow',
'elevator_state_and_passengers', 'staff_state_and_jobs',
'supplies_and_carried_goods', 'same_demand_route_improvement',
'normal_os_and_distinct_pid_resume')
OS_CHILD_TIMEOUT_SECONDS = 900
RECORDER = 'tests/fixtures/p23-03/passive_os_recorder.gd'
def collect_screenshots(out, pair, role):
"""Bind every external CUA screenshot to its request and decoded PNG.
def main():
print(json.dumps({'task_id': 'P23-03', 'status': 'blocked', 'engine_runs': 0,
'condition_matrix': condition_matrix(),
'required_infrastructure': ['run_all_tests.py', 'strict_test_markers.py',
'regression/owned_outer.py',
'regression/ordinary_proof_gate.py'],
'root_evidence': 'missing'}, ensure_ascii=False))
return 2
The external receipt supplies screenshots[{path,raw_sha256,tool_call_id}].
Paths are relative to the evidence root; raw receipt bytes remain unchanged.
"""
checker = module('p23_screenshot_binding', HERE/'check_p23_life_observation_evidence.py')
bindings = []
requests = json.loads((pair/(role+'-requests.json')).read_text())['requests']
for ack_name in requests:
ack_path = out/ack_name
checker.require(ack_path.resolve().is_relative_to(out.resolve()), 'ACK path escape')
ack = json.loads(ack_path.read_text())
request_path = ack_path.with_name('request.json')
request = json.loads(request_path.read_text())
receipt_path = out/ack['input_receipt']
checker.require(receipt_path.resolve().is_relative_to(out.resolve()), 'CUA receipt path escape')
cua = json.loads(receipt_path.read_text())
checker.require(ack['request_sha256'] == checker.sha(request_path), 'CUA request hash')
checker.require(cua['screenshots'], 'CUA screenshots missing')
for number, shot in enumerate(cua['screenshots']):
raw = out/shot['path']
checker.require(raw.resolve().is_relative_to(out.resolve()) and not raw.is_symlink(), 'screenshot path escape')
checker.require(checker.sha(raw) == shot['raw_sha256'] and shot['tool_call_id'], 'raw screenshot CUA binding')
kind, _, _ = checker.image_kind(raw.read_bytes())
conversion = None
if kind == 'jpeg':
png = ack_path.with_name('decoded-%02d.png' % number)
receipt = ack_path.with_name('conversion-%02d.json' % number)
convert_screenshot(raw.resolve(), png.resolve(), shot, receipt.resolve())
conversion = str(receipt.relative_to(out))
else:
png = raw
width, height, _ = checker.png_pixels(png.read_bytes())
bindings.append(dict(request=str(request_path.relative_to(out)), request_sha256=checker.sha(request_path),
sequence=request['sequence'], target_pid=request['target_pid'],
input_receipt=ack['input_receipt'], input_receipt_sha256=checker.sha(receipt_path),
screenshot_index=number, tool_call_id=shot['tool_call_id'],
raw_path=shot['path'], raw_sha256=shot['raw_sha256'], conversion=conversion,
png_path=str(png.relative_to(out)), png_sha256=checker.sha(png), width=width, height=height))
checker.require(bindings, 'empty screenshot collection')
destination = pair/(role+'-screenshots.json')
with destination.open('x') as output:
json.dump(bindings, output, indent=2)
output.write('\n')
return str(destination.relative_to(out))
def convert_screenshot(raw_path, png_path, cua_receipt, receipt_path):
"""Root-only conversion; raw CUA bytes are never renamed or rewritten."""
checker = module('p23_conversion_checker', HERE/'check_p23_life_observation_evidence.py')
raw, png, receipt = map(Path, (raw_path, png_path, receipt_path))
checker.require(raw.is_absolute() and png.is_absolute() and receipt.is_absolute(),
'absolute conversion paths required')
checker.require(raw.is_file() and not raw.is_symlink() and not png.exists()
and not receipt.exists() and raw.resolve() != png.resolve(), 'fresh separate conversion output')
raw_hash = checker.sha(raw)
checker.require(raw_hash == cua_receipt['raw_sha256'] and cua_receipt['tool_call_id'],
'CUA conversion input hash/call mismatch')
kind, width, height = checker.image_kind(raw.read_bytes())
checker.require(kind == 'jpeg', 'conversion requires JPEG input')
converter = '/usr/bin/sips'
converter_hash = checker.sha(converter)
argv = [converter, '-s', 'format', 'png', str(raw), '--out', str(png)]
process = subprocess.run(argv, capture_output=True, timeout=900, check=False)
rec = dict(raw_path=str(raw), raw_sha256=raw_hash, raw_kind=kind,
tool_call_id=cua_receipt['tool_call_id'], converter=converter,
converter_sha256=converter_hash, argv=argv, exit_code=process.returncode,
stdout_sha256=hashlib.sha256(process.stdout).hexdigest(),
stderr_sha256=hashlib.sha256(process.stderr).hexdigest(),
png_path=str(png), png_sha256=checker.sha(png) if png.is_file() else None,
width=width, height=height)
# Preserve failure receipts too. These logs allow independent hash checking.
with receipt.open('x') as output:
json.dump(rec, output, indent=2)
output.write('\n')
with Path(str(receipt) + '.stdout').open('xb') as output:
output.write(process.stdout)
with Path(str(receipt) + '.stderr').open('xb') as output:
output.write(process.stderr)
checker.require(checker.sha(raw) == raw_hash, 'raw CUA bytes changed during conversion')
checker.require(checker.sha(converter) == converter_hash, 'converter changed during conversion')
checker.inspect_conversion(rec, cua_receipt, converter_hash)
return rec
def collect_os(r, root, out, stage, engine, commit, refs, protected, outer):
"""Adapted P08-03 collect_os protocol, retaining owned-process guards.
Root supplies CUA ack/receipt files in the requested directories. Neither
controller nor passive endpoints generate input or change permissions.
"""
r.require(not outer.native.helper['live_engines'](), 'external engine present')
checker = module('p23_os_pins', HERE/'check_p23_life_observation_evidence.py')
before = r.inventory(stage, stage=True)
public = (stage/'project.godot').read_bytes()
(out/'os-pair').mkdir()
phases = []
for label in ('A-writer', 'A-reader', 'B-writer', 'B-reader'):
layout, role = label.split('-')
pair = out/'os-pair'/layout
pair.mkdir(exist_ok=role == 'reader')
r.require(not outer.native.helper['live_engines'](), 'external engine present')
r.require(r.source_refs(root) == refs and r.default_player(root) == protected, 'OS before guards')
r.require(r.inventory(stage, stage=True) == before, 'OS stage before guard')
r.require(shutil.disk_usage(out).free >= 500*1024*1024, 'OS disk admission')
folder = out/'os-runs'/label
folder.mkdir(parents=True)
name = 'TheTree-PCK-tests-'+uuid.uuid4().hex+'-p23-observation-'+label
profile = r.profile_path(name)
r.require(not profile.exists(), 'fresh OS private profile')
formal_source = pair/'formal' if role == 'reader' else None
formal_before = r.inventory(formal_source) if formal_source else {}
if role == 'reader':
r.require(set(formal_before) == {'world.bin','manifest.json','thumbnail.png','journal.log'}, 'all formal inputs')
formal = profile/'saves/manual_ui'
formal.mkdir(parents=True)
for member, pin in formal_before.items():
r.copy_source(formal_source/member, formal/member)
r.require(r.sha(formal/member) == pin['sha256'], 'formal fixture copy SHA')
recorder = folder/'passive_os.gd'
r.copy_source(root/RECORDER, recorder)
proof = folder/'teardown'
(proof/'owned-audio').mkdir(parents=True)
env = r.fresh_environment(dict(os.environ))
env.update(P23_OBSERVATION_ROOT=str(pair), P23_OBSERVATION_ROLE=role, P23_LAYOUT=layout,
THE_TREE_AUDIO_TEARDOWN_PROOF_DIR=str(proof/'owned-audio'),
THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF=str(proof/'control.json'))
command = [engine, '--path', str(stage), '--script', str(recorder)]
base = {'protocol':'p23-observation-native-os-v1', 'role':role, 'layout':layout, 'commit':commit,
'source_map':{n:v['sha256'] for n,v in refs.items()}, 'stage':str(stage),
'stage_before':before, 'profile':str(profile), 'profile_name':name,
'profile_before':{'existed':profile.exists(), 'files':r.inventory(profile) if profile.exists() else {}}, 'formal_input':formal_before,
'formal_source':str(formal_source) if formal_source else None, 'owner_pid':os.getpid(),
'default_before':protected, 'command':command, 'engine_sha256':r.sha(engine),
'controller_sha256':r.sha(root/'tools/run_p23_life_observation.py'),
'recorder_sha256':r.sha(recorder), 'formal_acceptance':False,
'timeout_seconds':OS_CHILD_TIMEOUT_SECONDS,
'inputs':checker.input_map(root), 'spec_sha256':checker.SPEC_SHA,
'settings_sha256':hashlib.sha256(public).hexdigest(),
'environment':{k:v for k,v in env.items() if k.startswith(('P23_', 'THE_TREE_'))},
'environment_sha256':hashlib.sha256(json.dumps(env,sort_keys=True).encode()).hexdigest()}
r.write(folder/'started.json', base)
error = None; children = []; guards = []; exit_code = None; owner_proof = None
try:
(stage/'project.godot').write_bytes(r.private_project(public, name))
with (folder/'engine-native.log').open('x') as log:
process = subprocess.Popen(command, cwd=stage, env=env, stdout=log, stderr=subprocess.STDOUT)
children.append(process)
r.write(folder/'process-started.json', dict(base, actual_pid=process.pid,
monotonic_ns=time.monotonic_ns(), state='waiting_for_root_cua_requests'))
print(json.dumps({'actual_pid':process.pid, 'request_directory':str(pair),
'state':'waiting_for_root_cua', 'role':role}), flush=True)
exit_code = process.wait(timeout=base['timeout_seconds'])
r.require(exit_code == 0, 'ordinary native OS exit0')
raw = (folder/'engine-native.log').read_text()
r.require('ERROR:' not in raw and 'SCRIPT ERROR:' not in raw, 'OS native diagnostics')
matches = re.findall(r'(?m)^P23 passive OS observation: ([0-9]+) checks, 0 failures$', raw)
r.require(len(matches) == 1 and int(matches[0]) > 0, 'passive observer completion')
observed = r.read(pair/(role+'-profile.json'))
r.require(observed['pid'] == process.pid and Path(observed['user_data_dir']).resolve() == profile.resolve()
and observed['display'] != 'headless', 'actual OS private profile/display')
collect_screenshots(out, pair, role)
owners = r.module('p23_os_owner_gate', r.SUPPORT/'ordinary_proof_gate.py')
owner_proof = owners.ordinary_gate(proof, process.pid, folder, ['single'])
except BaseException as caught:
error = type(caught).__name__+': '+str(caught)
finally:
try:
cleanup, cleanup_errors = outer.finish_captured(children)
finally:
(stage/'project.godot').write_bytes(public)
try:
r.require(r.source_refs(root) == refs, 'OS source unchanged')
r.require(r.inventory(stage, stage=True) == before, 'OS stage unchanged')
r.require(r.default_player(root) == protected, 'OS default player unchanged')
if formal_source:
r.require(r.inventory(formal_source) == formal_before, 'formal input unchanged')
r.require(r.sha(recorder) == base['recorder_sha256'], 'recorder unchanged')
except BaseException as caught:
guards.append(type(caught).__name__+': '+str(caught))
passed = not (error or guards or cleanup_errors) and len(children) == 1 and exit_code == 0
r.write(folder/'ended.json', dict(base, actual_pid=children[0].pid if children else None,
exit_code=exit_code, engine_ended=bool(cleanup) and all(v['engine_ended'] for v in cleanup),
error=error, guard_errors=guards, cleanup_errors=cleanup_errors, owned_cleanup=cleanup,
ordinary_owner_proof=owner_proof, stage_after=r.inventory(stage,stage=True),
engine_sha256_after=r.sha(engine), inputs_after=checker.input_map(root),
settings_sha256_after=hashlib.sha256((stage/'project.godot').read_bytes()).hexdigest(),
environment_after={k:v for k,v in env.items() if k.startswith(('P23_', 'THE_TREE_'))},
environment_sha256_after=hashlib.sha256(json.dumps(env,sort_keys=True).encode()).hexdigest(),
default_after=r.default_player(root), source_after=r.source_refs(root),
profile_after=r.inventory(profile), passed=passed))
phases.append('os-runs/'+label+'/ended.json')
r.require(passed, 'native OS collection failed: '+str(error or guards or cleanup_errors))
return phases
def module(name, path):
spec = importlib.util.spec_from_file_location(name, path)
obj = importlib.util.module_from_spec(spec)
spec.loader.exec_module(obj)
return obj
def absolute(value):
path = Path(value)
if not path.is_absolute():
raise argparse.ArgumentTypeError('absolute path required')
return path.resolve()
def collect(args):
root, out = args.project_root, args.output
if out.is_relative_to(root):
raise ValueError('evidence output must be outside the frozen source checkout')
r = module('p23_standard_runner', root/'tools/run_all_tests.py')
commit = subprocess.check_output(['git', '-C', str(root), 'rev-parse', 'HEAD'], text=True).strip()
r.require(commit == args.commit, 'actual final submitted commit required')
r.require(not subprocess.check_output(['git','-C',str(root),'status','--porcelain'], text=True), 'frozen clean worktree required')
r.require(not out.exists(), 'fresh output; never overwrite earlier proof')
refs = r.source_refs(root)
checker = module('p23_initial_inputs', root/'tools/check_p23_life_observation_evidence.py')
inputs_before = checker.input_map(root)
outer = r.module('p23_owned_outer', r.SUPPORT/'owned_outer.py')
r.require(not outer.native.helper['live_engines'](), 'one native engine')
engine = shutil.which(args.godot)
r.require(engine is not None, 'native engine exists')
engine = str(Path(engine).resolve())
out.mkdir(parents=True)
stage = out/'stage'; stage.mkdir()
(out/'starters').mkdir(); (out/'bootstrap').mkdir()
for name, pin in r.read(r.SUPPORT/'bootstrap-scripts.json').items():
data = pin['text'].encode()
r.require(hashlib.sha256(data).hexdigest() == pin['sha256'], 'bootstrap pin')
(out/'bootstrap'/name).write_bytes(data)
for name, pin in refs.items():
target = stage/name; target.parent.mkdir(parents=True, exist_ok=True)
r.copy_source(Path(pin['path']), target)
r.require(r.sha(target) == pin['sha256'], 'whole copied source '+name)
expected = {n: {'bytes':v['bytes'], 'sha256':v['sha256']} for n,v in refs.items()}
selected = {'root_before':refs, 'map':{n:v['sha256'] for n,v in refs.items()}, 'timeout':180}
r.require(r.inventory(stage, stage=True) == expected, 'complete private source')
protected = r.default_player(root); public = (stage/'project.godot').read_bytes()
paths = ['res://'+n for n in sorted(refs) if n.endswith('.gd')]
r.write(out/'parse-paths.json', paths)
markers = r.module('p23_strict_markers', root/'tools/strict_test_markers.py')
contracts = markers.load_contracts(root/CONTRACT)
policy = r.read(r.SUPPORT/'suite-policy.json')
diagnostics = r.module('p23_diagnostics', r.SUPPORT/'diagnostic_gate.py')
owners = r.module('p23_owners', r.SUPPORT/'ordinary_proof_gate.py')
audio = r.module('p23_audio', r.SUPPORT/'audio_evidence_gate.py')
r.write(out/'runner-input.json', {'protocol':'p23-normal-observation-v1', 'commit':commit,
'selected_source_map':selected['map'], 'engine':engine, 'engine_sha256':r.sha(engine),
'owner_pid':os.getpid(), 'default_player':protected, 'diagnostic_seed':806042,
'inputs':inputs_before, 'provenance':checker.input_provenance(root),
'pixel_decoder':{'interpreter':sys.executable,
'PIL_available':importlib.util.find_spec('PIL') is not None,
'raw_screenshots':'preserve bytes; Root lossless conversion required for JPEG without PIL'}})
phases = []; previous = {}; cases = []; error = None; os_proof = None
deadline = time.monotonic()+900
saved_env = {k:os.environ.get(k) for k in ('P23_LAYOUT','P23_OUTPUT')}
def phase(label, test):
remaining = int(deadline-time.monotonic())
r.require(remaining >= (180 if label == 'import' else 1), '900s collection deadline')
selected['timeout'] = min(180, remaining)
phase_out = out/'runs'/label
phase_out.mkdir(parents=True)
(phase_out/'starters').mkdir(); (phase_out/'bootstrap').mkdir()
for original in (out/'bootstrap').iterdir():
r.copy_source(original,phase_out/'bootstrap'/original.name)
r.require(r.sha(original)==r.sha(phase_out/'bootstrap'/original.name),'bootstrap full copy')
r.write(phase_out/'parse-paths.json',paths)
row = r.execute_phase(engine, stage, phase_out, label, test, selected, expected, previous,
policy, contracts, paths, public, protected, root, outer, markers, diagnostics, owners, audio)
# Supplement the existing runner receipt; preserve it verbatim.
r.write(phase_out/'p23-inputs.json', dict(source_map=selected['map'], source_after=r.source_refs(root),
engine_sha256=r.sha(engine), inputs=inputs_before, inputs_after=checker.input_map(root),
spec_sha256=checker.SPEC_SHA, settings_sha256=hashlib.sha256(public).hexdigest(),
stage_before=expected, stage_after=r.inventory(stage,stage=True),
default_before=protected, default_after=r.default_player(root),
receipt_sha256=r.sha(phase_out/(label+'-receipt.json'))))
phases.append('runs/'+label+'/'+label+'-receipt.json')
r.require(not row['failed'], 'strict phase failed: '+label)
try:
phase('import', None); phase('parse', None)
for layout in ('A', 'B'):
directory = out/('layout-'+layout)
os.environ.update(P23_LAYOUT=layout, P23_OUTPUT=str(directory))
phase('layout-'+layout, 'tests/ui/test_p23_life_observation.gd')
cases.append({'directory':directory.name, 'phase':phases[-1], 'files':r.inventory(directory)})
if args.input_mode == 'native_os':
os_phases = collect_os(r, root, out, stage, engine, commit, refs, protected, outer)
checker = module('p23_os_input_map', root/'tools/check_p23_life_observation_evidence.py')
os_proof = 'os-proof.json'
r.write(out/os_proof, {'source_map':selected['map'], 'inputs':checker.input_map(root),
'input_method':'native_os', 'observer_mutations':[], 'phases':os_phases, 'directory':'os-pair'})
except BaseException as caught:
error = type(caught).__name__+': '+str(caught)
finally:
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
for key,value in saved_env.items():
if value is None: os.environ.pop(key, None)
else: os.environ[key] = value
if r.source_refs(root) != refs or r.default_player(root) != protected:
error = (error+'; ' if error else '')+'protected root/profile changed'
if checker.input_map(root) != inputs_before:
error = (error+'; ' if error else '')+'spec/fixture/controller inputs changed'
r.write(out/'runner-ended.json', {'error':error, 'cleanup':cleanup, 'cleanup_errors':cleanup_errors,
'phases':phases, 'formal_acceptance':False, 'native_Windows':'pending'})
checker = module('p23_observation_checker', root/'tools/check_p23_life_observation_evidence.py')
r.write(out/'index.json', {'schema':'p23-life-observation-v1', 'commit':commit,
'source_map':selected['map'], 'input_map':inputs_before,
'provenance':checker.input_provenance(root),
'marker_contract_sha256':r.sha(root/CONTRACT),
'spec_sha256':hashlib.sha256((root/'docs/spec/acceptance/P23-03.json').read_text().strip().encode()).hexdigest(),
'cases':cases, 'phases':phases, 'conditions':list(CONDITIONS),
'artifacts':{str(p.relative_to(out)):r.sha(p)
for p in out.rglob('*') if p.is_file() and 'stage' not in p.relative_to(out).parts},
'os_proof':os_proof, 'regression':None,
'acceptance':'pending', 'collection_error':error})
return 1 if error or cleanup_errors else 0
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--project-root', type=absolute, required=True)
parser.add_argument('--output', type=absolute, required=True)
parser.add_argument('--commit', required=True)
parser.add_argument('--godot', default='godot')
parser.add_argument('--input-mode', choices=('handler', 'native_os'), default='handler')
parser.add_argument('--collect', action='store_true', required=True)
return collect(parser.parse_args(argv))
if __name__ == '__main__':
raise SystemExit(main())
+307
View File
@@ -3,11 +3,318 @@
import copy
import unittest
import sys
import contextlib
import io
import json
import hashlib
import struct
import zlib
import tempfile
from pathlib import Path
from unittest import mock
sys.dont_write_bytecode = True
import check_p23_life_observation_evidence as check
class ImageTests(unittest.TestCase):
def test_collector_conversion_is_bound_to_each_cua_screenshot(self):
runner = check.module('p23_unit_collector', check.ROOT/'tools/run_p23_life_observation.py')
with tempfile.TemporaryDirectory(dir=check.ROOT/'tests/fixtures/p23-03') as temporary:
root = Path(temporary)
pair = root/'os-pair/A'
request_dir = pair/'writer-request-0001'
request_dir.mkdir(parents=True)
request = request_dir/'request.json'
request.write_text(json.dumps(dict(sequence=1, target_pid=123)))
raw = request_dir/'raw.jpg'
raw.write_bytes(b'\xff\xd8unit-only\xff\xd9')
shot = dict(path=str(raw.relative_to(root)), raw_sha256=check.sha(raw), tool_call_id='unit-shot-call')
receipt = request_dir/'cua.json'
receipt.write_text(json.dumps(dict(screenshots=[shot])))
ack = request_dir/'ack.json'
ack.write_text(json.dumps(dict(request_sha256=check.sha(request), input_receipt=str(receipt.relative_to(root)))))
(pair/'writer-requests.json').write_text(json.dumps(dict(requests=[str(ack.relative_to(root))])))
def convert(raw_path, png_path, cua, receipt_path):
self.assertEqual(cua, shot)
self.assertEqual(Path(raw_path).read_bytes(), b'\xff\xd8unit-only\xff\xd9')
Path(png_path).write_bytes(self.png())
Path(receipt_path).write_text('{}')
with mock.patch.object(runner, 'module', return_value=check), \
mock.patch.object(check, 'image_kind', return_value=('jpeg', 2, 2)), \
mock.patch.object(runner, 'convert_screenshot', side_effect=convert) as converter:
output = runner.collect_screenshots(root, pair, 'writer')
converter.assert_called_once()
rows = json.loads((root/output).read_text())
self.assertEqual(rows[0]['tool_call_id'], shot['tool_call_id'])
self.assertEqual(rows[0]['raw_sha256'], check.sha(raw))
self.assertEqual(rows[0]['png_sha256'], check.sha(root/rows[0]['png_path']))
self.assertTrue(rows[0]['conversion'])
@staticmethod
def png(mode=0, channels=3):
def chunk(kind, body):
return struct.pack('>I', len(body)) + kind + body + struct.pack('>I', zlib.crc32(kind + body))
rows = [bytes([10, 20, 30, 40][:channels] * 2),
bytes([50, 60, 70, 80][:channels] * 2)]
filtered = bytearray()
prior = bytes(2 * channels)
for row in rows:
filtered.append(mode)
for x, value in enumerate(row):
left, up, corner = (row[x-channels] if x >= channels else 0,
prior[x], prior[x-channels] if x >= channels else 0)
p = left + up - corner
distances = [abs(p-left), abs(p-up), abs(p-corner)]
predictors = [0, left, up, (left+up)//2,
[left, up, corner][distances.index(min(distances))]]
filtered.append((value-predictors[mode]) % 256)
prior = row
header = struct.pack('>IIBBBBB', 2, 2, 8, 2 if channels == 3 else 6, 0, 0, 0)
return (b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', header)
+ chunk(b'IDAT', zlib.compress(filtered)) + chunk(b'IEND', b''))
def test_all_png_filters_rgb_and_rgba(self):
for mode in range(5):
for channels in (3, 4):
with self.subTest(mode=mode, channels=channels):
w, h, rows = check.png_pixels(self.png(mode, channels))
self.assertEqual((w, h), (2, 2))
self.assertEqual(rows[0][0], tuple([10, 20, 30, 40][:channels]))
self.assertEqual(rows[1][1], tuple([50, 60, 70, 80][:channels]))
def test_crc_and_trailing_bytes_rejected(self):
data = bytearray(self.png())
data[29] ^= 1
with self.assertRaisesRegex(ValueError, 'CRC mismatch'):
check.png_pixels(bytes(data))
with self.assertRaisesRegex(ValueError, 'invalid IEND'):
check.png_pixels(self.png() + b'extra')
def conversion(self):
raw, png = b'\xff\xd8synthetic-unit-test\xff\xd9', self.png()
digest = lambda b: hashlib.sha256(b).hexdigest()
rec = dict(raw_path='/raw.png', raw_sha256=digest(raw), raw_kind='jpeg',
tool_call_id='unit-test-call', converter='/usr/bin/sips', converter_sha256='a'*64,
argv=['/usr/bin/sips', '-s', 'format', 'png', '/raw.png', '--out', '/decoded.png'],
exit_code=0, stdout_sha256=digest(b''), stderr_sha256=digest(b''),
png_path='/decoded.png', png_sha256=digest(png), width=2, height=2)
cua = dict(raw_sha256=digest(raw), tool_call_id='unit-test-call')
return rec, cua, {'/raw.png': raw, '/decoded.png': png}
def test_conversion_chain_and_specific_forgery_deficits(self):
rec, cua, files = self.conversion()
helper = mock.Mock()
helper.jpeg_dimensions.return_value = (2, 2)
with mock.patch.object(Path, 'read_bytes', lambda p: files[str(p)]), \
mock.patch.object(check, 'module', return_value=helper):
self.assertEqual(check.inspect_conversion(rec, cua, 'a'*64)[:2], (2, 2))
for key, value, deficit in [('converter_sha256', 'b'*64, 'converter SHA mismatch'),
('argv', [], 'converter argv mismatch'),
('raw_sha256', 'b'*64, 'CUA raw hash/call mismatch'),
('width', 3, 'conversion dimensions mismatch')]:
with self.subTest(key=key):
bad = dict(rec, **{key: value})
with self.assertRaisesRegex(ValueError, deficit):
check.inspect_conversion(bad, cua, 'a'*64)
with self.assertRaisesRegex(ValueError, 'JPEG screenshot requires conversion record'):
check.screenshot_pixels('/raw.png')
class RestAuditTests(unittest.TestCase):
def fixture(self):
member = dict(staff_id='s', state='OFF_DUTY', health=100, current_task_id='', current_cell=[0, 0, 0])
facilities = [dict(instance_id=fid, definition_id='staff_waiting_room', origin=[0, 0, 0],
occupied_cells=[], state='OPEN', suspension_reason='') for fid in ('a', 'b')]
runtime = dict(staff=dict(staff=[member]), facilities=dict(instances=facilities),
defense=dict(raids=[]), events=dict(actor_locks={}), elevators=dict(trips=[]),
path=dict(cell_traffic=[dict(cell=[x, 0, 0]) for x in (-1, 0, 1)]),
world=dict(bounds=dict(min_x=-1, max_x=1, min_y=0, max_y=0), occupancies=[]))
routes = [dict(cell=[x, 0, 0], path=[[0, 0, 0], [x, 0, 0]] if x else [[0, 0, 0]],
edges=[dict(**{'from': [0, 0, 0], 'to': [x, 0, 0]}, total_cost=0)] if x else [],
calculate_path_cost=0) for x in (-1, 0, 1)]
observed = []
for f in facilities:
observed.append({k: f[k] for k in ('instance_id', 'definition_id', 'origin', 'occupied_cells')})
observed[-1].update(operation_state='OPERATING', probes=[dict(cell=[x, 0, 0], has_cell=True) for x in (0, -1, 1)],
ordered=[[x, 0, 0] for x in (-1, 0, 1)], routes=copy.deepcopy(routes))
selected = dict(reachable=True, resting=True, cell=[-1, 0, 0], facility_id='a', label='待機室で休憩中')
audit = dict(path_service_sha256=check.sha(check.ROOT/'scripts/world/path_service.gd'),
staff=[dict(member, defense_memberships=[], actor_lock=False, in_transit=False,
facilities=observed, comparison_only=selected)])
report = dict(resting_staff={'s': 'a'}, actor_drawings=[], layer=0, carried_patients={}, rooms={},
viewport=[1440, 810], zoom=1, pan=[0, 0])
return dict(systems=dict(runtime=runtime)), report, audit
def test_selection_ties_and_nonmutation(self):
state, report, audit = self.fixture()
before = copy.deepcopy((state, report, audit))
self.assertEqual(check.compare_rest(state, report, audit)['s']['cell'], [-1, 0, 0])
self.assertEqual((state, report, audit), before)
def test_rest_forgeries(self):
mutations = {
'selection': lambda a: a['staff'][0]['comparison_only'].update(facility_id='b'),
'tie': lambda a: a['staff'][0]['comparison_only'].update(cell=[0, 0, 0]),
'lock': lambda a: a['staff'][0].update(actor_lock=True),
'transit': lambda a: a['staff'][0].update(in_transit=True),
'label': lambda a: a['staff'][0]['comparison_only'].update(label='宿舎で休憩中'),
'cost': lambda a: a['staff'][0]['facilities'][0]['routes'][0]['edges'][0].update(total_cost=9),
'facility_order': lambda a: a['staff'][0]['facilities'].reverse(),
'candidate_order': lambda a: a['staff'][0]['facilities'][0]['ordered'].reverse(),
'empty_edge': lambda a: a['staff'][0]['facilities'][0]['routes'][0]['edges'][0].clear(),
}
for name, mutate in mutations.items():
with self.subTest(name=name):
state, report, audit = self.fixture()
mutate(audit)
with self.assertRaises(ValueError):
check.compare_rest(state, report, audit)
def test_ineligible_and_transit_recomputed(self):
state, report, audit = self.fixture()
state['systems']['runtime']['events']['actor_locks']['s'] = 'event'
audit['staff'][0].update(actor_lock=True, comparison_only={})
report['resting_staff'] = {}
self.assertEqual(check.compare_rest(state, report, audit), {'s': {}})
state, report, audit = self.fixture()
state['systems']['runtime']['elevators']['trips'] = [dict(actor_id='staff:s')]
audit['staff'][0]['in_transit'] = True
audit['staff'][0]['comparison_only'].update(resting=False, label='休憩先へ移動中')
report['resting_staff'] = {}
self.assertFalse(check.compare_rest(state, report, audit)['s']['resting'])
class InfrastructureTests(unittest.TestCase):
def test_matrix_requires_each_condition_and_each_category(self):
executed = {c: {k: set(v) for k, v in categories.items()} for c, categories in check.REQUIRED_CHECKS.items()}
self.assertTrue(all(r['status'] == 'passed' for r in check.condition_matrix(executed)))
for condition in check.CONDITIONS:
for category in check.CATEGORIES:
with self.subTest(condition=condition, category=category):
broken = copy.deepcopy(executed)
broken[condition][category].clear()
matrix = check.condition_matrix(broken)
self.assertEqual(next(r for r in matrix if r['condition'] == condition)['status'], 'pending')
self.assertIn(category, check.BLOCKERS(broken)[condition])
def test_distinct_pid_ack_and_formal_hash_negatives(self):
members = {name: 'a'*64 for name in ('world.bin', 'manifest.json', 'thumbnail.png', 'journal.log')}
writer = dict(actual_pid=1)
reader = dict(actual_pid=2, formal_input={k: dict(sha256=v) for k, v in members.items()})
saved = dict(pid=1, members=members)
records = [dict(request=dict(action='load_menu', target_pid=2, sequence=1, requested_ticks_usec=10, requested_unix_ns=90),
ack=dict(target_pid=2, sequence=1, request_sha256='b'*64, timestamp_ns=110),
observed=dict(pid=2, sequence=1, request_sha256='b'*64, ack_observed_ticks_usec=20, ack_observed_unix_ns=120, status='手動セーブを読み込みました。'),
cua=dict(target_pid=2, method='native_os', tool_call_id='actual-call', timestamp_ns=100),
request_sha256='b'*64)]
check.inspect_resume(writer, reader, saved, records, members)
for change in ('pid', 'ack', 'time', 'hash', 'id'):
with self.subTest(change=change):
r, q = copy.deepcopy(reader), copy.deepcopy(records)
if change == 'pid': r['actual_pid'] = 1
if change == 'ack': q.clear()
if change == 'time': q[0]['observed']['ack_observed_ticks_usec'] = 9
if change == 'hash': r['formal_input']['world.bin']['sha256'] = 'c'*64
if change == 'id': q[0]['ack']['sequence'] = 2
with self.assertRaises(ValueError): check.inspect_resume(writer, r, saved, q, members)
def test_rectangle_pixel_and_transform_negatives(self):
source = 'scripts/ui/pixel_people.gd'
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"', (check.ROOT/source).read_text())))
report = dict(rooms={}, landing_badges=[], cabins=[], resting_staff={}, badges=[],
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1)])
row = dict(category='staff', id='staff/s', rect=[1, 1, 16, 18], viewport_rect=[1, 1, 16, 18],
screen_rect=[1, 1, 16, 18], style_source=source, style_colors=colors)
audit = dict(rows=[row])
matrix = [1, 0, 0, 1, 0, 0]
image = (24, 24, [[(113, 157, 148)]*24 for _ in range(24)])
self.assertEqual(check.compare_rectangles(report, audit, matrix, matrix, matrix, image), ['staff/staff/s'])
for change in ('rectangle', 'pixels', 'transform', 'coverage', 'palette'):
with self.subTest(change=change):
a, m, img = copy.deepcopy(audit), matrix[:], image
if change == 'rectangle': a['rows'][0]['rect'][0] += 1
if change == 'pixels': img = (24, 24, [[(0, 0, 0)]*24 for _ in range(24)])
if change == 'transform': m[4] = 10
if change == 'coverage': a['rows'].clear()
if change == 'palette': a['rows'][0]['style_colors'] = ['000000']
with self.assertRaises(ValueError): check.compare_rectangles(report, a, matrix, m, matrix, img)
def test_interpolation_history_tampering(self):
state = dict(systems=dict(runtime=dict(guests=dict(guests=[]), staff=dict(staff=[]),
elevators=dict(shafts=[], trips=[], current_minute=0))))
report = dict(animation_time=0.0, paused=True, layer=0, zoom=1, pan=[0,0], viewport=[100,100],
actor_tracks={}, elevator_tracks={}, actor_drawings=[], cabins=[])
audit = dict(frames=[dict(frame=1, delta=.01, scaled_delta=0, display=copy.deepcopy(report)),
dict(frame=2, delta=.01, scaled_delta=0, display=copy.deepcopy(report))])
check.compare_motion(state, report, audit)
for change in ('delta', 'frame', 'display'):
with self.subTest(change=change):
bad = copy.deepcopy(audit)
if change == 'delta': bad['frames'][1]['scaled_delta'] = 1
if change == 'frame': bad['frames'][1]['frame'] = 1
if change == 'display': bad['frames'][1]['display']['animation_time'] = 1
with self.assertRaises(ValueError): check.compare_motion(state, report, bad)
def test_verify_only_missing_index_is_a_concrete_deficit(self):
with mock.patch.dict(check.os.environ, {'BUILD_OUTPUT_DIR': '/missing-p23-proof'}, clear=True):
with contextlib.redirect_stdout(io.StringIO()) as output:
self.assertEqual(check.main(['--verify-only']), 1)
result = json.loads(output.getvalue())
self.assertEqual(result['integrity'], 'fail')
self.assertEqual(result['acceptance'], 'pending')
self.assertIn('index.json', result['deficits'][0])
self.assertNotIn('blocked', result['deficits'][0])
def test_verify_only_requires_build_output(self):
with mock.patch.dict(check.os.environ, {}, clear=True):
with contextlib.redirect_stderr(io.StringIO()) as output:
self.assertEqual(check.main(['--verify-only']), 2)
self.assertIn('BUILD_OUTPUT_DIR is required', output.getvalue())
def test_normative_plan_change_invalidates_but_progress_does_not(self):
root = check.ROOT
text = (root / check.PLAN).read_text()
original = check.input_map(root)
progress = text.splitlines(keepends=True)
progress[2] = 'Progress-only test mutation\n'
with mock.patch.object(Path, 'read_text', return_value=''.join(progress)):
self.assertEqual(check.input_map(root), original)
changed = text.replace('同需要の動線改善を観察できる。', 'altered normative acceptance')
self.assertNotEqual(changed, text)
with mock.patch.object(Path, 'read_text', return_value=changed):
self.assertNotEqual(check.input_map(root), original)
self.assertNotIn(check.PLAN, original)
self.assertIn('full_plan_sha256', check.input_provenance(root))
for path in (root / 'tests/fixtures/p23-03').rglob('*'):
if path.is_file():
self.assertIn(path.relative_to(root).as_posix(), original)
def test_os_process_guards_reject_engine_profile_and_source_drift(self):
source = {'source.gd': 'a' * 64}
receipt = dict(engine_ended=True, passed=True, exit_code=0, error=None,
guard_errors=[], cleanup_errors=[], default_before={}, default_after={},
stage_before={}, stage_after={}, source_map=source,
source_after={'source.gd': {'sha256': 'a' * 64}},
owned_cleanup=[{'engine_ended': True}], ordinary_owner_proof={'present': True})
receipt.update(spec_sha256=check.SPEC_SHA, inputs={'spec': 'a'*64}, inputs_after={'spec': 'a'*64},
environment={'P23_LAYOUT': 'A'}, environment_after={'P23_LAYOUT': 'A'}, actual_pid=123,
profile='/private/test', profile_name='test', profile_before={'existed': False, 'files': {}}, profile_after={})
for field in ('engine_sha256', 'settings_sha256', 'environment_sha256'):
receipt[field] = receipt[field+'_after'] = 'a'*64
check.inspect_os_receipt(receipt, source)
for field, value in [('engine_ended', False), ('default_after', {'changed': True}),
('source_after', {'source.gd': {'sha256': 'b' * 64}}),
('stage_after', {'changed': True}), ('owned_cleanup', []),
('environment_after', {}), ('profile_before', {}),
('engine_sha256_after', 'b'*64), ('spec_sha256', 'b'*64)]:
with self.subTest(field=field):
altered = copy.deepcopy(receipt)
altered[field] = value
with self.assertRaises(ValueError):
check.inspect_os_receipt(altered, source)
class SemanticTests(unittest.TestCase):
def setUp(self):
self.guest = dict(guest_id='g', state='QUEUING', current_cell=[1, 0, 0],