P08-03: 配置の通常操作受入
This commit is contained in:
@@ -147,26 +147,79 @@ def input_map(root):
|
||||
R09_COMMIT = '31aa7706d28a92e313c432d5a62e35d19503dcc3'
|
||||
R09_DOCS = 'docs/plans/original-completion-2026-10-02/execution/p08-03'
|
||||
|
||||
def jpeg_dimensions(data):
|
||||
"""Parse baseline/progressive frame headers, including multi-scan markers.
|
||||
VALIDATORS = {'tools/check_p08_normal_placement_evidence.py',
|
||||
'tools/test_p08_normal_placement_evidence.py'}
|
||||
|
||||
This is a structural/dimension check, not a pixel decoder.
|
||||
"""
|
||||
def r09_allowed(name):
|
||||
return name in VALIDATORS or name.startswith(R09_DOCS+'/')
|
||||
|
||||
def pinned_document(pin):
|
||||
path = Path(pin['path'])
|
||||
require(path.stat().st_size == pin['bytes'] and sha(path) == pin['sha256'], 'collection pin SHA')
|
||||
return load(path)
|
||||
|
||||
def r09_runner_binding(evidence, index, pins):
|
||||
run = load(evidence/'runner-input.json')
|
||||
original = pinned_document(pins['runner_input'])
|
||||
require(run['engine'] == original['engine'] and run['engine_sha256'] == original['engine_sha256'],
|
||||
'R09 engine hash/version drift')
|
||||
require(run == original, 'R09 settings/config/environment drift')
|
||||
original_index = pinned_document(pins['index'])
|
||||
for role in ('writer', 'reader'):
|
||||
receipt = load(evidence/('os-runs/'+role+'/ended.json'))
|
||||
pinned = pinned_document(pins['OS'][role]['receipt'])
|
||||
require(receipt == pinned, 'R09 settings/config/environment drift')
|
||||
name = 'os-pair/'+role+'-profile.json'
|
||||
require(index['artifacts'][name] == original_index['artifacts'][name]
|
||||
and sha(evidence/name) == original_index['artifacts'][name]['sha256'],
|
||||
'R09 engine hash/version drift')
|
||||
require(run['commit'] == index['commit'] == R09_COMMIT, 'R09 reuse candidate')
|
||||
|
||||
def r09_applicability(root, index):
|
||||
head = subprocess.check_output(['git', '-C', str(root), 'rev-parse', 'HEAD'], text=True).strip()
|
||||
require(index['commit'] == R09_COMMIT, 'R09 reuse candidate')
|
||||
result = subprocess.run(['git', '-C', str(root), 'merge-base', '--is-ancestor', R09_COMMIT, head],
|
||||
capture_output=True)
|
||||
require(result.returncode == 0, 'R09_COMMIT not an ancestor')
|
||||
diff = subprocess.check_output(['git', '-C', str(root), 'diff', '--name-status',
|
||||
'--no-renames', '-z', R09_COMMIT, head]).decode().split('\0')
|
||||
require(all(r09_allowed(name) for name in diff[1::2] if name), 'non-whitelisted committed change')
|
||||
status = subprocess.check_output(['git', '-C', str(root), 'status', '--porcelain', '-z']).decode().split('\0')
|
||||
require(all(len(row) >= 4 and r09_allowed(row[3:]) and 'R' not in row[:2]
|
||||
and 'C' not in row[:2] for row in status if row), 'dirty working tree outside whitelist')
|
||||
require(input_map(root) == index['inputs'], 'stale reuse input')
|
||||
actual = {n:v['sha256'] for n,v in runner.source_refs(root).items()}
|
||||
require({n:v for n,v in actual.items() if not r09_allowed(n)} ==
|
||||
{n:v for n,v in index['source_map'].items() if not r09_allowed(n)}, 'stale reuse source')
|
||||
for name in VALIDATORS:
|
||||
baseline = subprocess.check_output(['git', '-C', str(root), 'show', R09_COMMIT+':'+name])
|
||||
require(index['source_map'][name] == hashlib.sha256(baseline).hexdigest(), 'reuse source pin')
|
||||
pins = load(root/R09_DOCS/'ROOT_NATIVE_R09_COLLECTION_PINS.json')
|
||||
original = Path(pins['original_root'])
|
||||
pinned = pinned_document(pins['runner_input'])
|
||||
current = load(original/'runner-input.json')
|
||||
require(current == pinned, 'R09 runner input drift')
|
||||
require(pinned['commit'] == index['commit'] and pinned['inputs'] == index['inputs'],
|
||||
'R09 runner binding drift')
|
||||
require(sha(pinned['engine']) == pinned['engine_sha256'], 'R09 engine hash drift')
|
||||
for role in ('writer', 'reader'):
|
||||
receipt = pinned_document(pins['OS'][role]['receipt'])
|
||||
require(receipt['engine_sha256'] == pinned['engine_sha256'], 'R09 engine hash/version drift')
|
||||
require(receipt['environment_sha256'] == hashlib.sha256(
|
||||
json.dumps(receipt['environment'], sort_keys=True).encode()).hexdigest(),
|
||||
'R09 settings/environment drift')
|
||||
return {name:sha(root/name) for name in sorted(VALIDATORS)}
|
||||
|
||||
def jpeg_dimensions(data):
|
||||
"""Structural validation only; positive validity requires original capture hashes."""
|
||||
require(data[:2] == b'\xff\xd8' and data[-2:] == b'\xff\xd9', 'invalid JPEG SOI/EOI')
|
||||
pos, dimensions, scans = 2, None, 0
|
||||
entropy = False
|
||||
components, quant, huffman = {}, set(), set()
|
||||
while pos < len(data):
|
||||
if entropy:
|
||||
pos = data.find(b'\xff', pos)
|
||||
require(pos >= 0, 'truncated JPEG scan')
|
||||
require(data[pos] == 255, 'invalid JPEG marker')
|
||||
while pos < len(data) and data[pos] == 255:
|
||||
pos += 1
|
||||
while pos < len(data) and data[pos] == 255: pos += 1
|
||||
require(pos < len(data), 'truncated JPEG marker')
|
||||
marker = data[pos]; pos += 1
|
||||
if entropy and (marker == 0 or 0xd0 <= marker <= 0xd7):
|
||||
continue
|
||||
entropy = False
|
||||
if marker == 0xd9:
|
||||
require(pos == len(data) and dimensions is not None and scans > 0, 'invalid JPEG end')
|
||||
return dimensions
|
||||
@@ -176,21 +229,59 @@ def jpeg_dimensions(data):
|
||||
length = int.from_bytes(data[pos:pos+2], 'big')
|
||||
require(length >= 2 and pos+length <= len(data)-2, 'truncated JPEG segment')
|
||||
body = data[pos+2:pos+length]; pos += length
|
||||
if marker in (0xc0, 0xc2):
|
||||
if marker == 0xdb:
|
||||
offset = 0
|
||||
while offset < len(body):
|
||||
info = body[offset]; offset += 1
|
||||
require(info >> 4 in (0, 1) and info & 15 <= 3, 'invalid JPEG DQT')
|
||||
size = 64 * (1 + (info >> 4))
|
||||
require(offset+size <= len(body), 'invalid JPEG DQT')
|
||||
quant.add(info & 15); offset += size
|
||||
elif marker == 0xc4:
|
||||
offset = 0
|
||||
while offset < len(body):
|
||||
require(offset+17 <= len(body), 'invalid JPEG DHT')
|
||||
info = body[offset]; count = sum(body[offset+1:offset+17])
|
||||
require(info >> 4 in (0, 1) and info & 15 <= 3 and count > 0
|
||||
and offset+17+count <= len(body), 'invalid JPEG DHT')
|
||||
huffman.add((info >> 4, info & 15)); offset += 17+count
|
||||
elif marker in (0xc0, 0xc2):
|
||||
require(dimensions is None and len(body) >= 6 and body[0] == 8
|
||||
and body[5] > 0 and len(body) == 6+3*body[5], 'invalid JPEG SOF')
|
||||
height, width = struct.unpack('>HH', body[1:5])
|
||||
require(width > 0 and height > 0, 'invalid JPEG dimensions')
|
||||
components = {body[i]:body[i+2] for i in range(6, len(body), 3)}
|
||||
require(len(components) == body[5], 'invalid JPEG SOF components')
|
||||
dimensions = (width, height)
|
||||
elif 0xc0 <= marker <= 0xcf and marker not in (0xc4, 0xc8, 0xcc):
|
||||
raise ValueError('unsupported JPEG SOF')
|
||||
elif marker == 0xda:
|
||||
require(dimensions is not None and body and body[0] > 0
|
||||
and len(body) == 4+2*body[0], 'invalid JPEG SOS')
|
||||
scans += 1; entropy = True
|
||||
ids = [body[i] for i in range(1, 1+2*body[0], 2)]
|
||||
require(len(ids) == len(components) and set(ids) == set(components),
|
||||
'JPEG SOF/SOS component mismatch')
|
||||
require(set(components.values()) <= quant, 'missing JPEG DQT')
|
||||
require(bool(huffman), 'missing JPEG DHT')
|
||||
for i in range(2, 1+2*body[0], 2):
|
||||
table = body[i]
|
||||
require((0, table >> 4) in huffman and (1, table & 15) in huffman,
|
||||
'JPEG SOS undefined table')
|
||||
start, nonempty = pos, False
|
||||
while pos < len(data):
|
||||
if data[pos] != 255:
|
||||
nonempty = True; pos += 1; continue
|
||||
require(pos+1 < len(data), 'truncated JPEG scan')
|
||||
if data[pos+1] == 0:
|
||||
nonempty = True; pos += 2; continue
|
||||
if 0xd0 <= data[pos+1] <= 0xd7:
|
||||
pos += 2; continue
|
||||
break
|
||||
require(nonempty and pos > start, 'empty JPEG entropy stream')
|
||||
scans += 1
|
||||
raise ValueError('missing JPEG EOI')
|
||||
|
||||
def r09_images(evidence, index, measurement):
|
||||
def r09_images(evidence, index, measurement, pins=None):
|
||||
require(measurement['original_index_sha256'] == sha(evidence/'index.json'), 'stale image index pin')
|
||||
expected = {f'os-pair/{role}-request-{i:04d}/screen-{side}.png'
|
||||
for role, count in (('reader', 5), ('writer', 64))
|
||||
@@ -199,11 +290,34 @@ def r09_images(evidence, index, measurement):
|
||||
indexed = {n for n in index['artifacts'] if n.startswith('os-pair/') and n.endswith('.png')
|
||||
and n not in ('os-pair/formal/thumbnail.png', 'os-pair/formal.bak/thumbnail.png')}
|
||||
require(set(rows) == indexed == expected, 'missing/extra image entry')
|
||||
original_index = None
|
||||
if pins is not None:
|
||||
original_index = pinned_document(pins['index'])
|
||||
for role in ('writer', 'reader'):
|
||||
pinned_document(pins['OS'][role]['receipt'])
|
||||
sizes = {}
|
||||
for name in sorted(expected):
|
||||
data = (evidence/name).read_bytes()
|
||||
sizes[name] = jpeg_dimensions(data)
|
||||
row, pin = rows[name], index['artifacts'][name]
|
||||
if original_index is not None:
|
||||
capture = original_index['artifacts'][name]
|
||||
original_bytes = (Path(pins['original_root'])/name).read_bytes()
|
||||
require(hashlib.sha256(original_bytes).hexdigest() == capture['sha256']
|
||||
and len(original_bytes) == capture['bytes']
|
||||
and pin == capture and hashlib.sha256(data).hexdigest() == capture['sha256']
|
||||
and len(data) == capture['bytes'], 'original capture hash mismatch')
|
||||
require(row['source_sha256'] == capture['sha256'], 'measurement capture hash altered')
|
||||
role = name.split('/')[1].split('-')[0]
|
||||
ack_name = name.rsplit('/', 1)[0]+'/ack.json'
|
||||
ack_path = Path(pins['original_root'])/ack_name
|
||||
require(sha(ack_path) == original_index['artifacts'][ack_name]['sha256'],
|
||||
'original capture receipt hash mismatch')
|
||||
ack = load(ack_path)
|
||||
receipt = pinned_document(pins['OS'][role]['receipt'])
|
||||
side = 'screen_before' if name.endswith('screen-before.png') else 'screen_after'
|
||||
require(ack[side] == name and ack['target_pid'] == receipt['actual_pid'],
|
||||
'original capture receipt mismatch')
|
||||
require(hashlib.sha256(data).hexdigest() == row['source_sha256'] == pin['sha256']
|
||||
and len(data) == pin['bytes'], 'image source_sha256 mismatch')
|
||||
require(row['format_by_magic'] == 'JPEG' and type(row['width']) is int
|
||||
@@ -466,22 +580,13 @@ def check(project_root, evidence_dir):
|
||||
original = evidence
|
||||
pins_path = root/R09_DOCS/'ROOT_NATIVE_R09_COLLECTION_PINS.json'
|
||||
if pins_path.exists():
|
||||
r09_applicability(root, index)
|
||||
r09_runner_binding(evidence, index, load(pins_path))
|
||||
original = r09_collection(evidence, index, load(pins_path))
|
||||
r09_images(evidence, index, load(root/R09_DOCS/'ROOT_R09_READONLY_IMAGE_DIMENSIONS.json'))
|
||||
# Only these Python validator/test files may differ from same31aa.
|
||||
changed = {'tools/check_p08_normal_placement_evidence.py',
|
||||
'tools/test_p08_normal_placement_evidence.py'}
|
||||
added = {'tests/fixtures/p08-03/repair13_images.py'}
|
||||
for name in changed:
|
||||
baseline = subprocess.check_output(['git', '-C', str(root), 'show', R09_COMMIT+':'+name])
|
||||
require(index['source_map'][name] == hashlib.sha256(baseline).hexdigest(), 'reuse source pin')
|
||||
require({n:v for n,v in actual.items() if n not in changed|added} ==
|
||||
{n:v for n,v in index['source_map'].items() if n not in changed}, 'stale reuse source')
|
||||
require({n:v for n,v in input_map(root).items() if n not in added} == index['inputs'], 'stale reuse input')
|
||||
r09_images(evidence, index, load(root/R09_DOCS/'ROOT_R09_READONLY_IMAGE_DIMENSIONS.json'), load(pins_path))
|
||||
actual = index['source_map']
|
||||
require(index['source_map'] == actual, 'stale source')
|
||||
require(index['inputs'] == {n:v for n,v in input_map(root).items()
|
||||
if not pins_path.exists() or n != 'tests/fixtures/p08-03/repair13_images.py'}, 'stale input')
|
||||
require(index['inputs'] == input_map(root), 'stale input')
|
||||
require(index['spec_sha256'] == SPEC_SHA == hashlib.sha256((root/SPEC).read_text().strip().encode()).hexdigest(), 'immutable spec')
|
||||
require(index['marker_contract_sha256'] == sha(root/CONTRACT), 'stale marker')
|
||||
require(re.fullmatch('[0-9a-f]{40}', index['commit']) is not None, 'commit')
|
||||
|
||||
Reference in New Issue
Block a user