65 lines
3.4 KiB
Python
65 lines
3.4 KiB
Python
"""Producer non-execution readback; independent review remains separate."""
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import zipfile
|
|
from datetime import datetime, timezone
|
|
|
|
BASE = Path(__file__).resolve().parent
|
|
NAME = 'ArcadiaStudio-0.2.0-preview.contextual.20260911-x86_64-pc-windows-gnullvm'
|
|
ZIP = BASE / 'packages' / (NAME + '.zip')
|
|
STAGE = BASE / 'packages' / NAME
|
|
EXPECTED = '9186d4fc9a7b52fb195c183c4847c8e3419ea6da8b3f907e0c06f9ceec58dee0'
|
|
|
|
def sha(path):
|
|
with path.open('rb') as stream:
|
|
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
|
|
|
assert sha(ZIP) == EXPECTED
|
|
result = {'read_at': datetime.now(timezone.utc).isoformat(),
|
|
'archive': str(ZIP), 'archive_sha256': EXPECTED, 'archive_bytes': ZIP.stat().st_size}
|
|
actual = {}
|
|
with zipfile.ZipFile(ZIP) as archive:
|
|
names = archive.namelist()
|
|
assert len(names) == len(set(names)) == len({n.casefold() for n in names}) == 559
|
|
for entry in archive.infolist():
|
|
assert entry.filename.startswith(NAME + '/') and not entry.is_dir()
|
|
relative = entry.filename[len(NAME) + 1:]
|
|
assert '..' not in Path(relative).parts and not Path(relative).is_absolute()
|
|
assert entry.date_time == (2000, 1, 1, 0, 0, 0)
|
|
with archive.open(entry) as stream:
|
|
checksum = hashlib.file_digest(stream, 'sha256').hexdigest()
|
|
assert checksum == sha(STAGE / relative), relative
|
|
actual[relative] = {'bytes': entry.file_size, 'sha256': checksum}
|
|
assert set(actual) == {p.relative_to(STAGE).as_posix() for p in STAGE.rglob('*') if p.is_file()}
|
|
expected_manifest = {}
|
|
for line in (STAGE / 'SHA256SUMS.txt').read_text(encoding='utf-8').splitlines():
|
|
checksum, path = line.split(' ', 1)
|
|
assert path not in expected_manifest
|
|
expected_manifest[path] = checksum
|
|
assert set(expected_manifest) == set(actual) - {'SHA256SUMS.txt'}
|
|
for path, checksum in expected_manifest.items():
|
|
assert actual[path]['sha256'] == checksum
|
|
package = json.loads((STAGE / 'package.json').read_text(encoding='utf-8-sig'))
|
|
notices = json.loads((STAGE / 'dependency-notices.json').read_text(encoding='utf-8-sig'))
|
|
docs = json.loads((STAGE / 'documentation-check.json').read_text(encoding='utf-8-sig'))
|
|
prepared = json.loads((BASE / 'preparation.json').read_text(encoding='utf-8'))
|
|
for path, checksum in prepared['gui_inputs'].items():
|
|
assert sha(BASE / 'source' / path) == checksum
|
|
for path, checksum in prepared['server_inputs'].items():
|
|
assert sha(BASE / 'source' / path) == checksum
|
|
for path, checksum in prepared['shared_non_cargo_inputs'].items():
|
|
assert sha(BASE / 'source' / path) == checksum
|
|
assert not subprocess.check_output(['git', 'status', '--porcelain'], cwd=BASE / 'source')
|
|
result.update({'files': actual, 'file_count': len(actual), 'manifest_entries': len(expected_manifest),
|
|
'dependency_packages': len(notices['packages']), 'notice_roots': notices['root_packages'],
|
|
'documentation_check': docs, 'binary_imports': package['imports'],
|
|
'source_commit': prepared['reconstruction_commit'], 'gui_inputs_unchanged': 238,
|
|
'server_inputs_unchanged': 120, 'shared_inputs_unchanged': 5,
|
|
'source_worktree_clean': True, 'scope': 'Producer readback only; no extraction or executable/runtime launch.'})
|
|
with (BASE / 'package-readback.json').open('x', encoding='utf-8', newline='\n') as output:
|
|
json.dump(result, output, indent=2)
|
|
output.write('\n')
|
|
print(json.dumps({k: v for k, v in result.items() if k != 'files'}, indent=2))
|