package-voc-product-fields.py 5.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. """Seal the fixed product restoration with verified existing evidence; no network."""
  2. import argparse
  3. import ast
  4. import hashlib
  5. import importlib.util
  6. import json
  7. from pathlib import Path
  8. import shutil
  9. import sys
  10. import zipfile
  11. ROOT = Path(__file__).resolve().parents[1]
  12. def sha(raw):
  13. return hashlib.sha256(raw).hexdigest()
  14. def main():
  15. p = argparse.ArgumentParser(description=__doc__)
  16. p.add_argument('--out', type=Path, default=ROOT / 'artifacts/商品历史缺项恢复-20261106-r1')
  17. a = p.parse_args()
  18. out = a.out.absolute()
  19. if out.exists():
  20. raise ValueError('NEW_PACKAGE_DIRECTORY_REQUIRED')
  21. work = ROOT / 'work/voc-product-restoration-20261106-r1'
  22. package = out / 'package'
  23. shutil.copytree(work / 'prepared', package)
  24. tool = ROOT / 'scripts/restore-voc-product-fields.py'
  25. shutil.copyfile(tool, package / tool.name)
  26. instruction = ROOT / 'docs/industry-voc-monitoring-20261106/30-发给运维001-商品历史缺项定向恢复.md'
  27. shutil.copyfile(instruction, package / '发给运维001.md')
  28. shutil.copyfile(instruction, out / '发给运维001.md')
  29. (package / 'verification').mkdir()
  30. for name in ('local-simulation.json', 'source-availability.json'):
  31. shutil.copyfile(work / name, package / 'verification' / name)
  32. local = json.loads((work / 'local-simulation.json').read_text(encoding='utf-8'))
  33. assert local['ok'] and all(local['checks'].values())
  34. assert local['before']['analysis'] == local['after']['analysis'] == 771
  35. assert local['before']['strict'] == local['after']['strict'] == {'analysis': 18, 'audit': 39}
  36. release = json.loads((package / 'RELEASE.json').read_text(encoding='utf-8'))
  37. release.update(pythonMinimum='3.9', python39GrammarVerified=True, nativePython39RuntimeVerified=False,
  38. productionInstalled=False, productionImported=False, databaseIncluded=False, credentialsIncluded=False,
  39. skillFiles=0, staticFiles=0, backendModuleReplacements=0, collectionCalls=0,
  40. expectedGeneration='8857847197722f25c7392c4df85c8ab4427f3329a80a1fd36aad585f123eae93',
  41. expectedDatabaseSha256='634e7809f26d4bcf6422da03bb4d5742e279ae1c787d770531953245eeaf0754',
  42. productsChanged=60, fieldCounts=local['fieldCounts'], localSimulationChecks=local['checks'],
  43. localFixtureIsProductionDatabase=False, targetProductionDryRequired=True,
  44. currentKnownConflictsOverwritten=0, originalReferencesVerified=152, originalReferencesStillUnavailable=73,
  45. fixedToolSha256=sha(tool.read_bytes()),
  46. testCommand='py -3.14 -X utf8 -B scripts/test_restore_voc_product_fields.py -v',
  47. testResult={'tests': 5, 'passed': 5,
  48. 'coverage': ['raw original identity and exact fields', 'staged restore preserves all other business rows',
  49. 'changed current row rejected', 'dry apply status replay rollback and publication guards',
  50. 'tampered source rejected']})
  51. (package / 'RELEASE.json').write_text(json.dumps(release, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  52. ast.parse(tool.read_text(encoding='utf-8'), feature_version=(3, 9))
  53. spec = importlib.util.spec_from_file_location('sealed_repair', package / tool.name)
  54. repair = importlib.util.module_from_spec(spec)
  55. spec.loader.exec_module(repair)
  56. v, u = repair.module_check(ROOT / 'server/voc-library')
  57. plan = repair.plan(package, v)
  58. assert len(plan['products']) == 60 and sum(plan['fields'].values()) == 70
  59. (package / 'verification/fixed-field-plan.json').write_text(json.dumps(plan, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  60. files = sorted(f for f in package.rglob('*') if f.is_file())
  61. assert not any(f.suffix in ('.sqlite', '.db', '.key', '.pyc') or f.name.startswith('.env') for f in files)
  62. for f in files:
  63. raw = f.read_bytes()
  64. import re
  65. assert not re.search(rb'\bsk-[A-Za-z0-9_-]{20,}|Bearer\s+[A-Za-z0-9._-]{20,}', raw), 'POSSIBLE_CREDENTIAL:' + str(f)
  66. manifest = ''.join(sha(f.read_bytes()) + ' ' + f.relative_to(package).as_posix() + '\n' for f in files)
  67. (package / 'FILES.sha256').write_text(manifest, encoding='utf-8')
  68. archive = out / 'ksf-voc-product-fields-20261106-r1.zip'
  69. with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED, compresslevel=9) as z:
  70. for f in [*files, package / 'FILES.sha256']:
  71. z.write(f, f.relative_to(package).as_posix())
  72. with zipfile.ZipFile(archive) as z:
  73. assert z.testzip() is None
  74. for line in z.read('FILES.sha256').decode().splitlines():
  75. expected, name = line.split(' ', 1)
  76. assert sha(z.read(name)) == expected
  77. receipt = {'ok': True, 'fileName': archive.name, 'bytes': archive.stat().st_size,
  78. 'sha256': sha(archive.read_bytes()), 'filesVerified': len(files), 'zipEntries': len(files) + 1,
  79. 'productionImported': False, 'nativePython39RuntimeVerified': False,
  80. 'localTestsPassed': 5, 'productsChanged': 60, 'businessMissingFields': 7,
  81. 'historicalCoverageFields': 60, 'supportingClassificationFields': 3, 'collectionCalls': 0}
  82. (out / 'delivery-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  83. print(json.dumps(receipt, ensure_ascii=False, indent=2))
  84. if __name__ == '__main__':
  85. main()