P08-03: 配置の通常操作受入
This commit is contained in:
@@ -1,6 +1,50 @@
|
|||||||
# P08-03 condition matrix — implementation submission, not acceptance
|
# P08-03 condition matrix — implementation submission, not acceptance
|
||||||
|
|
||||||
## Repair13 current status (supersedes earlier submission status below)
|
## Repair14 current submission
|
||||||
|
|
||||||
|
Executed native source commit: 31aa7706d28a92e313c432d5a62e35d19503dcc3.
|
||||||
|
Current candidate commit: **to be bound by Runner**.
|
||||||
|
Executed source/input/engine hashes are recorded separately in
|
||||||
|
implementation.json executed_native_binding; current validator_only_changes
|
||||||
|
never replaces the executed source map. Earlier repair history remains historical.
|
||||||
|
|
||||||
|
All five conditions remain: **Root native evidence collected at 31aa;
|
||||||
|
independent semantic acceptance pending; Root final full175 regression pending;
|
||||||
|
integration pending**. No acceptance or earlier PASS is inherited.
|
||||||
|
|
||||||
|
Strict reuse checks require the R09 ancestor, complete committed and working-tree
|
||||||
|
whitelists, exact inputs and non-whitelisted sources, R09 validator blobs and
|
||||||
|
SHA-pinned runner/OS receipts and engine bytes. The committed R09..HEAD diff
|
||||||
|
contains A tests/fixtures/p08-03/repair13_images.py. Deleting the fixture does
|
||||||
|
not erase that history: applicability fails and a Root re-run is required.
|
||||||
|
No fixture inputs are excluded.
|
||||||
|
|
||||||
|
JPEG positives use only actual R09 capture bytes, compared to the SHA-pinned
|
||||||
|
original index, measurement, and original OS acknowledgements/role receipts.
|
||||||
|
The structural parser checks referenced DQT/DHT, SOF/SOS component identity,
|
||||||
|
non-empty entropy and EOI; synthetic JPEG data exists only in negative tests
|
||||||
|
inside the Python test module.
|
||||||
|
|
||||||
|
Open semantic findings: preview/runtime stair difference; coordinator runtime
|
||||||
|
acceptance=false; Windows same-distribution/device. Required remaining work:
|
||||||
|
Root R09 positive hash re-check; Root re-run for failed reuse applicability;
|
||||||
|
Root final full175; independent Opus review of all five conditions;
|
||||||
|
integration/current final verification/ledger; Windows same-distribution/device
|
||||||
|
(no Mac substitution). The denominator30/88 and immutable spec are unchanged.
|
||||||
|
Repair counter14 is historical; repair/inference/material limits remain null.
|
||||||
|
|
||||||
|
Final local validation: python3 tools/test_p08_normal_placement_evidence.py:
|
||||||
|
107 tests in 84.026 seconds, exit0, no failures or skips. Two Python AST
|
||||||
|
checks and git diff --check passed. Earlier failed helper-renaming attempts
|
||||||
|
were corrected; no native, Godot, full regression or verify-only run occurred.
|
||||||
|
Focused final controls: 18 tests passed, including actual original capture
|
||||||
|
hashes. Root positive re-check remains required.
|
||||||
|
|
||||||
|
Validator-only SHA-256:
|
||||||
|
- tools/check_p08_normal_placement_evidence.py: 301098ced37bf83bd580af8b0a73e32f776157737ec6a55513c8cb5f63cd10f6
|
||||||
|
- tools/test_p08_normal_placement_evidence.py: 24a6894d7e944f5819641adc1bf2ce7a2fba5b367f1005bbae83775060498283
|
||||||
|
|
||||||
|
## Repair13 historical status (superseded by Repair14)
|
||||||
|
|
||||||
Checker/test-only repair. Product, Runtime, Godot, collector, same31aa and rawR09
|
Checker/test-only repair. Product, Runtime, Godot, collector, same31aa and rawR09
|
||||||
bytes are unchanged. Root supplied the read-only **post-collection** sips
|
bytes are unchanged. Root supplied the read-only **post-collection** sips
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -147,26 +147,79 @@ def input_map(root):
|
|||||||
R09_COMMIT = '31aa7706d28a92e313c432d5a62e35d19503dcc3'
|
R09_COMMIT = '31aa7706d28a92e313c432d5a62e35d19503dcc3'
|
||||||
R09_DOCS = 'docs/plans/original-completion-2026-10-02/execution/p08-03'
|
R09_DOCS = 'docs/plans/original-completion-2026-10-02/execution/p08-03'
|
||||||
|
|
||||||
def jpeg_dimensions(data):
|
VALIDATORS = {'tools/check_p08_normal_placement_evidence.py',
|
||||||
"""Parse baseline/progressive frame headers, including multi-scan markers.
|
'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')
|
require(data[:2] == b'\xff\xd8' and data[-2:] == b'\xff\xd9', 'invalid JPEG SOI/EOI')
|
||||||
pos, dimensions, scans = 2, None, 0
|
pos, dimensions, scans = 2, None, 0
|
||||||
entropy = False
|
components, quant, huffman = {}, set(), set()
|
||||||
while pos < len(data):
|
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')
|
require(data[pos] == 255, 'invalid JPEG marker')
|
||||||
while pos < len(data) and data[pos] == 255:
|
while pos < len(data) and data[pos] == 255: pos += 1
|
||||||
pos += 1
|
|
||||||
require(pos < len(data), 'truncated JPEG marker')
|
require(pos < len(data), 'truncated JPEG marker')
|
||||||
marker = data[pos]; pos += 1
|
marker = data[pos]; pos += 1
|
||||||
if entropy and (marker == 0 or 0xd0 <= marker <= 0xd7):
|
|
||||||
continue
|
|
||||||
entropy = False
|
|
||||||
if marker == 0xd9:
|
if marker == 0xd9:
|
||||||
require(pos == len(data) and dimensions is not None and scans > 0, 'invalid JPEG end')
|
require(pos == len(data) and dimensions is not None and scans > 0, 'invalid JPEG end')
|
||||||
return dimensions
|
return dimensions
|
||||||
@@ -176,21 +229,59 @@ def jpeg_dimensions(data):
|
|||||||
length = int.from_bytes(data[pos:pos+2], 'big')
|
length = int.from_bytes(data[pos:pos+2], 'big')
|
||||||
require(length >= 2 and pos+length <= len(data)-2, 'truncated JPEG segment')
|
require(length >= 2 and pos+length <= len(data)-2, 'truncated JPEG segment')
|
||||||
body = data[pos+2:pos+length]; pos += length
|
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
|
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')
|
and body[5] > 0 and len(body) == 6+3*body[5], 'invalid JPEG SOF')
|
||||||
height, width = struct.unpack('>HH', body[1:5])
|
height, width = struct.unpack('>HH', body[1:5])
|
||||||
require(width > 0 and height > 0, 'invalid JPEG dimensions')
|
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)
|
dimensions = (width, height)
|
||||||
elif 0xc0 <= marker <= 0xcf and marker not in (0xc4, 0xc8, 0xcc):
|
elif 0xc0 <= marker <= 0xcf and marker not in (0xc4, 0xc8, 0xcc):
|
||||||
raise ValueError('unsupported JPEG SOF')
|
raise ValueError('unsupported JPEG SOF')
|
||||||
elif marker == 0xda:
|
elif marker == 0xda:
|
||||||
require(dimensions is not None and body and body[0] > 0
|
require(dimensions is not None and body and body[0] > 0
|
||||||
and len(body) == 4+2*body[0], 'invalid JPEG SOS')
|
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')
|
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')
|
require(measurement['original_index_sha256'] == sha(evidence/'index.json'), 'stale image index pin')
|
||||||
expected = {f'os-pair/{role}-request-{i:04d}/screen-{side}.png'
|
expected = {f'os-pair/{role}-request-{i:04d}/screen-{side}.png'
|
||||||
for role, count in (('reader', 5), ('writer', 64))
|
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')
|
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')}
|
and n not in ('os-pair/formal/thumbnail.png', 'os-pair/formal.bak/thumbnail.png')}
|
||||||
require(set(rows) == indexed == expected, 'missing/extra image entry')
|
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 = {}
|
sizes = {}
|
||||||
for name in sorted(expected):
|
for name in sorted(expected):
|
||||||
data = (evidence/name).read_bytes()
|
data = (evidence/name).read_bytes()
|
||||||
sizes[name] = jpeg_dimensions(data)
|
sizes[name] = jpeg_dimensions(data)
|
||||||
row, pin = rows[name], index['artifacts'][name]
|
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']
|
require(hashlib.sha256(data).hexdigest() == row['source_sha256'] == pin['sha256']
|
||||||
and len(data) == pin['bytes'], 'image source_sha256 mismatch')
|
and len(data) == pin['bytes'], 'image source_sha256 mismatch')
|
||||||
require(row['format_by_magic'] == 'JPEG' and type(row['width']) is int
|
require(row['format_by_magic'] == 'JPEG' and type(row['width']) is int
|
||||||
@@ -466,22 +580,13 @@ def check(project_root, evidence_dir):
|
|||||||
original = evidence
|
original = evidence
|
||||||
pins_path = root/R09_DOCS/'ROOT_NATIVE_R09_COLLECTION_PINS.json'
|
pins_path = root/R09_DOCS/'ROOT_NATIVE_R09_COLLECTION_PINS.json'
|
||||||
if pins_path.exists():
|
if pins_path.exists():
|
||||||
|
r09_applicability(root, index)
|
||||||
|
r09_runner_binding(evidence, index, load(pins_path))
|
||||||
original = r09_collection(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'))
|
r09_images(evidence, index, load(root/R09_DOCS/'ROOT_R09_READONLY_IMAGE_DIMENSIONS.json'), load(pins_path))
|
||||||
# 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')
|
|
||||||
actual = index['source_map']
|
actual = index['source_map']
|
||||||
require(index['source_map'] == actual, 'stale source')
|
require(index['source_map'] == actual, 'stale source')
|
||||||
require(index['inputs'] == {n:v for n,v in input_map(root).items()
|
require(index['inputs'] == input_map(root), 'stale input')
|
||||||
if not pins_path.exists() or n != 'tests/fixtures/p08-03/repair13_images.py'}, 'stale input')
|
|
||||||
require(index['spec_sha256'] == SPEC_SHA == hashlib.sha256((root/SPEC).read_text().strip().encode()).hexdigest(), 'immutable spec')
|
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(index['marker_contract_sha256'] == sha(root/CONTRACT), 'stale marker')
|
||||||
require(re.fullmatch('[0-9a-f]{40}', index['commit']) is not None, 'commit')
|
require(re.fullmatch('[0-9a-f]{40}', index['commit']) is not None, 'commit')
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import io
|
|||||||
import os
|
import os
|
||||||
import gzip
|
import gzip
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import struct
|
||||||
|
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
@@ -163,6 +164,10 @@ class EvidenceTests(unittest.TestCase):
|
|||||||
self.edit('index.json', lambda d: d.update(commit='0'*40))
|
self.edit('index.json', lambda d: d.update(commit='0'*40))
|
||||||
self.reject('stale commit')
|
self.reject('stale commit')
|
||||||
|
|
||||||
|
def test_non_reuse_head_mismatch(self):
|
||||||
|
self.edit('index.json', lambda d:d.update(commit='0'*40))
|
||||||
|
self.reject('stale commit')
|
||||||
|
|
||||||
def test_stale_input(self):
|
def test_stale_input(self):
|
||||||
(self.root/'docs/traceability/original/BASELINE.txt').write_text('changed')
|
(self.root/'docs/traceability/original/BASELINE.txt').write_text('changed')
|
||||||
self.reject('stale input')
|
self.reject('stale input')
|
||||||
@@ -524,11 +529,54 @@ class EvidenceTests(unittest.TestCase):
|
|||||||
def test_mock_native_timeout_restores_project_and_cleans_owned_child(self):
|
def test_mock_native_timeout_restores_project_and_cleans_owned_child(self):
|
||||||
self.collector_mock(fail_wait=True)
|
self.collector_mock(fail_wait=True)
|
||||||
|
|
||||||
|
def image_put(path, value):
|
||||||
|
path.write_text(json.dumps(value, sort_keys=True)+'\n')
|
||||||
|
|
||||||
|
def negative_jpeg(width=1280, height=752, progressive=False):
|
||||||
|
def segment(marker, body):
|
||||||
|
return bytes((255, marker))+struct.pack('>H', len(body)+2)+body
|
||||||
|
frame = segment(0xc2 if progressive else 0xc0,
|
||||||
|
b'\x08'+struct.pack('>HH', height, width)+b'\x01\x01\x11\x00')
|
||||||
|
scan = segment(0xda, b'\x01\x01\x00\x00\x3f\x00')+b'\x12\xff\x00\x34\xff\xd0\x56'
|
||||||
|
tables = segment(0xdb, b'\x00'+bytes([1])*64)+segment(0xc4, b'\x00'+b'\x01'+bytes(15)+b'\x00'+b'\x10'+b'\x01'+bytes(15)+b'\x00')
|
||||||
|
return b'\xff\xd8'+tables+segment(0xe0, b'SYNTHETIC')+frame+scan+(scan if progressive else b'')+b'\xff\xd9'
|
||||||
|
|
||||||
|
def negative_images(base, candidate):
|
||||||
|
out, original = base/'canonical', base/'original'
|
||||||
|
out.mkdir(parents=True); index = {'commit':candidate, 'artifacts':{}}
|
||||||
|
measurement = {'scope':'synthetic post-collection measurement', 'images':[]}
|
||||||
|
for role, count in (('reader',5), ('writer',64)):
|
||||||
|
for i in range(1,count+1):
|
||||||
|
for side in ('before','after'):
|
||||||
|
name = f'os-pair/{role}-request-{i:04d}/screen-{side}.png'
|
||||||
|
path = out/name; path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_bytes(negative_jpeg(progressive=i%2 == 0))
|
||||||
|
measurement['images'].append({'file':name, 'source_sha256':hashlib.sha256(path.read_bytes()).hexdigest(),
|
||||||
|
'width':1280,'height':752,'format_by_magic':'JPEG'})
|
||||||
|
for name in ('runner-input.json','runner-ended.json','os-runs/writer/ended.json','os-runs/reader/ended.json'):
|
||||||
|
path = out/name; path.parent.mkdir(parents=True,exist_ok=True); image_put(path, {'synthetic':True})
|
||||||
|
refresh_images(out, index, measurement)
|
||||||
|
shutil.copytree(out, original)
|
||||||
|
def pin(name):
|
||||||
|
p=original/name
|
||||||
|
return {'path':str(p),'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()}
|
||||||
|
pins = {'original_root':str(original),'native_candidate':candidate,'index':pin('index.json'),
|
||||||
|
'runner_input':pin('runner-input.json'),'runner_ended':pin('runner-ended.json'),
|
||||||
|
'OS':{role:{'receipt':pin('os-runs/'+role+'/ended.json')} for role in ('writer','reader')}}
|
||||||
|
return out, original, index, measurement, pins
|
||||||
|
|
||||||
|
def refresh_images(out, index, measurement):
|
||||||
|
index['artifacts'] = {p.relative_to(out).as_posix():{'bytes':p.stat().st_size,
|
||||||
|
'sha256':hashlib.sha256(p.read_bytes()).hexdigest()} for p in out.rglob('*') if p.is_file() and p.name != 'index.json'}
|
||||||
|
image_put(out/'index.json',index)
|
||||||
|
measurement['original_index_sha256'] = hashlib.sha256((out/'index.json').read_bytes()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
class R09ImageTests(unittest.TestCase):
|
class R09ImageTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temp = tempfile.TemporaryDirectory(dir=ROOT/'tests/fixtures/p08-03/.scratch')
|
self.temp = tempfile.TemporaryDirectory(dir=ROOT/'tests/fixtures/p08-03/.scratch')
|
||||||
self.addCleanup(self.temp.cleanup)
|
self.addCleanup(self.temp.cleanup)
|
||||||
self.fixture = module('repair13_image_fixture', ROOT/'tests/fixtures/p08-03/repair13_images.py')
|
self.fixture = SimpleNamespace(make=negative_images, jpeg=negative_jpeg, refresh=refresh_images)
|
||||||
self.out, self.original, self.index, self.measurement, self.pins = self.fixture.make(Path(self.temp.name), C.R09_COMMIT)
|
self.out, self.original, self.index, self.measurement, self.pins = self.fixture.make(Path(self.temp.name), C.R09_COMMIT)
|
||||||
|
|
||||||
def images(self):
|
def images(self):
|
||||||
@@ -541,9 +589,29 @@ class R09ImageTests(unittest.TestCase):
|
|||||||
self.fixture.refresh(self.out, self.index, self.measurement)
|
self.fixture.refresh(self.out, self.index, self.measurement)
|
||||||
|
|
||||||
def test_baseline_progressive_and_collection_positive(self):
|
def test_baseline_progressive_and_collection_positive(self):
|
||||||
self.images()
|
pins = C.load(ROOT/C.R09_DOCS/'ROOT_NATIVE_R09_COLLECTION_PINS.json')
|
||||||
self.assertEqual(self.original, C.r09_collection(self.out,self.index,self.pins))
|
original = Path(pins['original_root'])
|
||||||
self.assertEqual((1280,752),C.jpeg_dimensions(self.fixture.jpeg(progressive=True)))
|
if not (original/'index.json').exists():
|
||||||
|
self.skipTest('Root-executed original R09 capture hash re-check required')
|
||||||
|
C.r09_images(original, C.load(original/'index.json'),
|
||||||
|
C.load(ROOT/C.R09_DOCS/'ROOT_R09_READONLY_IMAGE_DIMENSIONS.json'), pins)
|
||||||
|
|
||||||
|
def test_jpeg_missing_tables_and_scan(self):
|
||||||
|
data = negative_jpeg()
|
||||||
|
def remove(marker):
|
||||||
|
start = data.index(bytes((255, marker)))
|
||||||
|
size = int.from_bytes(data[start+2:start+4], 'big')
|
||||||
|
return data[:start]+data[start+2+size:]
|
||||||
|
for stream, error in ((remove(0xdb), 'missing JPEG DQT'),
|
||||||
|
(remove(0xc4), 'missing JPEG DHT'),
|
||||||
|
(data[:data.index(b'\xff\xda')+10]+b'\xff\xd9', 'empty JPEG entropy stream')):
|
||||||
|
with self.subTest(error=error), self.assertRaisesRegex(ValueError, error):
|
||||||
|
C.jpeg_dimensions(stream)
|
||||||
|
start = data.index(b'\xff\xda')
|
||||||
|
for offset, value, error in ((5, 2, 'JPEG SOF/SOS component mismatch'),
|
||||||
|
(6, 0x11, 'JPEG SOS undefined table')):
|
||||||
|
stream = bytearray(data); stream[start+offset] = value
|
||||||
|
with self.assertRaisesRegex(ValueError, error): C.jpeg_dimensions(stream)
|
||||||
|
|
||||||
def test_truncated_or_non_jpeg(self):
|
def test_truncated_or_non_jpeg(self):
|
||||||
good = self.fixture.jpeg()
|
good = self.fixture.jpeg()
|
||||||
@@ -585,6 +653,22 @@ class R09ImageTests(unittest.TestCase):
|
|||||||
self.measurement['images'][0]['source_sha256'] = '0'*64
|
self.measurement['images'][0]['source_sha256'] = '0'*64
|
||||||
with self.assertRaisesRegex(ValueError,'source_sha256 mismatch'): self.images()
|
with self.assertRaisesRegex(ValueError,'source_sha256 mismatch'): self.images()
|
||||||
|
|
||||||
|
def test_original_capture_hash_and_measurement_drift(self):
|
||||||
|
for alter_bytes in (True, False):
|
||||||
|
with self.subTest(alter_bytes=alter_bytes):
|
||||||
|
out, original, index, measurement, pins = negative_images(
|
||||||
|
Path(self.temp.name)/('case-'+str(alter_bytes)), C.R09_COMMIT)
|
||||||
|
row = sorted(measurement['images'], key=lambda r:r['file'])[0]
|
||||||
|
if alter_bytes:
|
||||||
|
(out/row['file']).write_bytes(negative_jpeg(height=720))
|
||||||
|
refresh_images(out, index, measurement)
|
||||||
|
error = 'original capture hash mismatch'
|
||||||
|
else:
|
||||||
|
row['source_sha256'] = '0'*64
|
||||||
|
error = 'measurement capture hash altered'
|
||||||
|
with self.assertRaisesRegex(ValueError, error):
|
||||||
|
C.r09_images(out, index, measurement, pins)
|
||||||
|
|
||||||
def test_stale_index_pin(self):
|
def test_stale_index_pin(self):
|
||||||
self.measurement['original_index_sha256'] = '0'*64
|
self.measurement['original_index_sha256'] = '0'*64
|
||||||
with self.assertRaisesRegex(ValueError,'stale image index pin'): self.images()
|
with self.assertRaisesRegex(ValueError,'stale image index pin'): self.images()
|
||||||
@@ -600,6 +684,61 @@ class R09ImageTests(unittest.TestCase):
|
|||||||
C.r09_collection(self.out,self.index,self.pins)
|
C.r09_collection(self.out,self.index,self.pins)
|
||||||
|
|
||||||
|
|
||||||
|
class R09ApplicabilityTests(unittest.TestCase):
|
||||||
|
def test_commit_and_ancestor(self):
|
||||||
|
with patch.object(C.subprocess, 'check_output', return_value=C.R09_COMMIT+'\n'):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'R09 reuse candidate'):
|
||||||
|
C.r09_applicability(ROOT, {'commit':'0'*40})
|
||||||
|
with patch.object(C.subprocess, 'run', return_value=SimpleNamespace(returncode=1)):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'R09_COMMIT not an ancestor'):
|
||||||
|
C.r09_applicability(ROOT, {'commit':C.R09_COMMIT})
|
||||||
|
|
||||||
|
def test_non_whitelisted_committed_and_dirty_changes(self):
|
||||||
|
names = ['scenes/main/main.gd', 'tests/ui/test_p08_normal_placement_ops.gd',
|
||||||
|
'tests/fixtures/p08-03/added.py', 'tests/fixtures/p08-03/placement.gd',
|
||||||
|
'tools/run_p08_normal_placement.py', 'tools/regression/suite-policy.json']
|
||||||
|
for name in names:
|
||||||
|
for status in ('A', 'D', 'M'):
|
||||||
|
with self.subTest(name=name, status=status):
|
||||||
|
with patch.object(C.subprocess, 'run', return_value=SimpleNamespace(returncode=0)), patch.object(
|
||||||
|
C.subprocess, 'check_output', side_effect=[
|
||||||
|
C.R09_COMMIT+'\n', (status+'\0'+name+'\0').encode()]):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'non-whitelisted committed change'):
|
||||||
|
C.r09_applicability(ROOT, {'commit':C.R09_COMMIT})
|
||||||
|
with patch.object(C.subprocess, 'run', return_value=SimpleNamespace(returncode=0)), patch.object(
|
||||||
|
C.subprocess, 'check_output', side_effect=[C.R09_COMMIT+'\n', b'', b' M scenes/main/main.gd\0']):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'dirty working tree outside whitelist'):
|
||||||
|
C.r09_applicability(ROOT, {'commit':C.R09_COMMIT})
|
||||||
|
|
||||||
|
def test_engine_settings_environment_drift(self):
|
||||||
|
pins = {'runner_input':{'tag':'run'}, 'index':{'tag':'index'},
|
||||||
|
'OS':{'writer':{'receipt':{'tag':'receipt'}}}}
|
||||||
|
baseline = {'engine':'pinned-engine', 'engine_sha256':'1'*64}
|
||||||
|
for field, message in (('engine_sha256', 'R09 engine hash/version drift'),
|
||||||
|
('settings', 'R09 settings/config/environment drift'),
|
||||||
|
('environment', 'R09 settings/config/environment drift')):
|
||||||
|
altered = dict(baseline); altered[field] = 'drift'
|
||||||
|
with self.subTest(field=field), patch.object(C, 'load', return_value=altered), patch.object(
|
||||||
|
C, 'pinned_document', return_value=baseline):
|
||||||
|
with self.assertRaisesRegex(ValueError, message):
|
||||||
|
C.r09_runner_binding(ROOT, {}, pins)
|
||||||
|
# Version is in the SHA-pinned profile, not a worker engine invocation.
|
||||||
|
run = dict(baseline, commit=C.R09_COMMIT)
|
||||||
|
documents = [run, {}]
|
||||||
|
originals = [run, {'artifacts':{'os-pair/writer-profile.json':{'sha256':'1'*64}}}, {}]
|
||||||
|
with patch.object(C, 'load', side_effect=documents), patch.object(
|
||||||
|
C, 'pinned_document', side_effect=originals):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'R09 engine hash/version drift'):
|
||||||
|
C.r09_runner_binding(ROOT, {'artifacts':{'os-pair/writer-profile.json':{'sha256':'2'*64}}}, pins)
|
||||||
|
|
||||||
|
def test_input_drift(self):
|
||||||
|
with patch.object(C.subprocess, 'run', return_value=SimpleNamespace(returncode=0)), patch.object(
|
||||||
|
C.subprocess, 'check_output', side_effect=[C.R09_COMMIT+'\n', b'', b'']), patch.object(
|
||||||
|
C, 'input_map', return_value={'fixture':'changed'}):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'stale reuse input'):
|
||||||
|
C.r09_applicability(ROOT, {'commit':C.R09_COMMIT, 'inputs':{'fixture':'original'}})
|
||||||
|
|
||||||
|
|
||||||
class RealR05InfrastructureTests(unittest.TestCase):
|
class RealR05InfrastructureTests(unittest.TestCase):
|
||||||
"""Actual native snapshot, never a fabricated acceptance specimen."""
|
"""Actual native snapshot, never a fabricated acceptance specimen."""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user