split-voc-database-delivery.py 5.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. """Produce two small ordinary ZIP attachments, then verify exact reconstruction."""
  2. import hashlib
  3. import importlib.util
  4. import json
  5. from pathlib import Path
  6. import shutil
  7. import zipfile
  8. ROOT = Path(__file__).resolve().parents[1]
  9. SOURCE = Path('F:/文档/康师傅skill/ksf-voc-database-20261106-r1.zip')
  10. OUT = ROOT / 'artifacts/研究数据库分包投递-20261106'
  11. WORK = ROOT / 'work/voc-database-transport-20261106'
  12. sha = lambda b: hashlib.sha256(b).hexdigest()
  13. def main():
  14. source = SOURCE.read_bytes()
  15. expected = '1ba1ac2fa6a8b774f8945d88e014c07dd5e03332792af298a41f015ba6946f9f'
  16. if len(source) != 25913256 or sha(source) != expected:
  17. raise ValueError('SOURCE_HASH_MISMATCH')
  18. with zipfile.ZipFile(SOURCE) as z:
  19. assert len(z.namelist()) == 17 and z.testzip() is None
  20. manifest = z.read('FILES.sha256').decode('utf-8').splitlines()
  21. assert len(manifest) == 16
  22. for line in manifest:
  23. h, name = line.split(' ', 1)
  24. assert sha(z.read(name)) == h
  25. if OUT.exists() or WORK.exists():
  26. raise ValueError('TRANSPORT_OUTPUT_ALREADY_EXISTS')
  27. OUT.mkdir(parents=True)
  28. WORK.mkdir(parents=True)
  29. chunks = [source[:14000000], source[14000000:]]
  30. transport = {'schema': 'ksf.voc-database.transport.v1', 'filename': SOURCE.name,
  31. 'bytes': len(source), 'sha256': sha(source), 'parts': [
  32. {'index': i, 'chunk': SOURCE.name + f'.part{i:02}', 'bytes': len(data), 'sha256': sha(data)}
  33. for i, data in enumerate(chunks, 1)]}
  34. common = {'TRANSPORT.json': (json.dumps(transport, ensure_ascii=False, indent=2) + '\n').encode(),
  35. 'reassemble-voc-database.py': (ROOT / 'scripts/reassemble-voc-database.py').read_bytes(),
  36. 'README.md': (ROOT / 'docs/voc-database-delivery-20261106/发给运维001-数据库分包投递.md').read_bytes()}
  37. parts = []
  38. for i, data in enumerate(chunks, 1):
  39. p = OUT / (SOURCE.stem + f'-part{i:02}.zip')
  40. with zipfile.ZipFile(p, 'x') as z:
  41. for name, value in {**common, transport['parts'][i-1]['chunk']: data}.items():
  42. info = zipfile.ZipInfo(name, (2026, 11, 6, 0, 0, 0))
  43. info.external_attr = 0o100600 << 16
  44. z.writestr(info, value, compress_type=zipfile.ZIP_STORED if name.endswith(f'.part{i:02}') else zipfile.ZIP_DEFLATED)
  45. with zipfile.ZipFile(p) as z:
  46. assert z.testzip() is None and len(z.namelist()) == 4
  47. assert p.stat().st_size < 20000000
  48. parts.append({'filename': p.name, 'bytes': p.stat().st_size, 'sha256': sha(p.read_bytes())})
  49. spec = importlib.util.spec_from_file_location('reassemble', ROOT / 'scripts/reassemble-voc-database.py')
  50. reassembler = importlib.util.module_from_spec(spec)
  51. spec.loader.exec_module(reassembler)
  52. inputs = [OUT / p['filename'] for p in parts]
  53. target = WORK / SOURCE.name
  54. result = reassembler.reassemble(list(reversed(inputs)), target)
  55. assert result['ok'] and result['fileCreated'] and target.read_bytes() == source
  56. stamp = target.stat().st_mtime_ns
  57. repeat = reassembler.reassemble(inputs, target)
  58. assert repeat['operation'] == 'unchanged' and target.stat().st_mtime_ns == stamp
  59. for bad, code in ((inputs[:1], 'EXACTLY_TWO_PARTS_REQUIRED'), ([inputs[0], inputs[0]], 'PART_MISSING_OR_DUPLICATED')):
  60. try:
  61. reassembler.reassemble(bad, WORK / SOURCE.name)
  62. except ValueError as exc:
  63. assert str(exc) == code
  64. else:
  65. raise AssertionError('BAD_INPUT_WAS_ACCEPTED')
  66. # A same-size altered chunk must fail and must not leave a final or temporary file.
  67. corrupt = WORK / 'corrupt-part01.zip'
  68. with zipfile.ZipFile(inputs[0]) as original, zipfile.ZipFile(corrupt, 'x') as changed:
  69. for name in original.namelist():
  70. data = original.read(name)
  71. if name.endswith('.part01'):
  72. data = bytes([data[0] ^ 1]) + data[1:]
  73. changed.writestr(name, data)
  74. bad_dir = WORK / 'bad-output'
  75. bad_dir.mkdir()
  76. try:
  77. reassembler.reassemble([corrupt, inputs[1]], bad_dir / SOURCE.name)
  78. except ValueError as exc:
  79. assert str(exc) == 'PART_HASH_MISMATCH'
  80. else:
  81. raise AssertionError('CORRUPTED_PART_WAS_ACCEPTED')
  82. assert not list(bad_dir.iterdir())
  83. receipt = {'ok': True, 'source': {'filename': SOURCE.name, 'bytes': len(source), 'sha256': sha(source)},
  84. 'attachments': parts, 'sourceManifestVerified': '16/16', 'transportZipEntriesEach': 4,
  85. 'allAttachmentsUnder20000000Bytes': True,
  86. 'validation': {'byteIdenticalReassembly': True, 'reversedInputs': True, 'repeatNoOpMtimePreserved': True,
  87. 'missingAndDuplicatedInputsRejected': True, 'tamperedPartRejectedNoFinalFile': True},
  88. 'installed': False, 'productionWrites': 0, 'originalPackageModified': False}
  89. (OUT / 'transport-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  90. shutil.copyfile(ROOT / 'docs/voc-database-delivery-20261106/发给运维001-数据库分包投递.md', OUT / '发给运维001-数据库分包投递.md')
  91. shutil.copyfile(ROOT / 'scripts/reassemble-voc-database.py', OUT / 'reassemble-voc-database.py')
  92. print(json.dumps(receipt, ensure_ascii=False, indent=2))
  93. if __name__ == '__main__':
  94. main()