| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- """Package the unsaved S3 v2 display fix; never call business services."""
- from pathlib import Path
- import copy
- import hashlib
- import json
- import zipfile
- ROOT = Path(__file__).resolve().parents[1]
- DOCS = Path('F:/文档/工作坊当前资料')
- STAGE = ROOT / 'work/first-live-S3-v2-package'
- DELIVERY = DOCS / 'ksf-live-S3-v2-20261103.zip'
- RECEIPT = ROOT / 'work/first-live-S3-v2-package-receipt.json'
- def sha(data):
- return hashlib.sha256(data).hexdigest()
- def as_json(value):
- return (json.dumps(value, ensure_ascii=False, indent=2) + '\n').encode('utf-8')
- def write_new(path, data):
- with path.open('xb') as output:
- output.write(data)
- assert not STAGE.exists() and not DELIVERY.exists() and not RECEIPT.exists()
- v1_bytes = (ROOT / 'work/first-live-S3-template.json').read_bytes()
- v2_bytes = (ROOT / 'work/first-live-S3-v2-template.json').read_bytes()
- v1, v2 = json.loads(v1_bytes), json.loads(v2_bytes)
- assert sha(v1_bytes) == '987024e3a1d37e9ef17ee6bfdced24947e76d67c326027d6f92dddebb95d5478'
- assert sha(v2_bytes) == '41c32974519799103709fb124115ecd79a1ed2ef1b3b343c129e23cb2be9ca49'
- assert len(v2_bytes) == 21973 and v2['sessionId'] is None and v2['version'] == 2
- expected = copy.deepcopy(v1)
- expected['version'] = 2
- expected['report']['chapters'][2]['sections'].append({
- 'kind': 'table', 'title': '待补问题',
- 'data': {'cols': ['序号', '待核实问题'],
- 'rows': [[str(i + 1), q] for i, q in enumerate(v1['pendingQuestions'])]}
- })
- assert v2 == expected, 'Only version and pending-question display table may change'
- assert len(v2['rawText']) == 2554 and len(v2['pendingQuestions']) == 8
- assert len(v2['sourceIds']) == len(v2['report']['evidence']) == 4
- assert 'opportunity' not in v2 and 'projectId' not in v2
- preview = json.loads((ROOT / 'work/live-S3-v2-template-preview-20261103-02.json').read_bytes())
- assert preview['ok'] and preview['previewOnly'] and preview['templatePreviewVerified']
- assert preview['savedArtifactId'] is None and preview['businessWrites'] == 0
- assert preview['browserVerified'] is False
- assert len(preview['checks']) == 114 and all(x['passed'] for x in preview['checks'])
- assert preview['evidenceDrawersChecked'] == 8 and preview['errors'] == []
- preview_receipt = {key: preview[key] for key in [
- 'ok', 'operation', 'at', 'browserVerified', 'templatePreviewVerified', 'previewOnly',
- 'savedArtifactId', 'scope', 'readOnly', 'businessWrites', 'viewports',
- 'evidenceDrawersChecked', 'quoteComparison', 'checks', 'errors'
- ]}
- preview_receipt['templateSha256'] = sha(v2_bytes)
- preview_receipt['note'] = '未保存模板的隔离浏览器内存预览;不是第2版生产页面验收。原始截图与完整记录由Codex本地保留。'
- payload = {
- 'S3-save-v2.template.json': v2_bytes,
- '34-S3第1版独立验收与字段说明.md': (DOCS / '34-S3第1版独立验收与字段说明.md').read_bytes(),
- '36-发给王刚001-S3第2版保存.txt': (DOCS / '36-发给王刚001-S3第2版保存.txt').read_bytes(),
- 'REPORT-PREVIEW.txt': ('S3第2版展示修复预览(未保存)\n'
- '只补页面待补问题表;以下rawText与第1版完全相同。\n\n' + v2['rawText']).encode('utf-8'),
- 'TEMPLATE-PREVIEW-VERIFY.json': as_json(preview_receipt),
- }
- manifest = {
- 'package': 'first-live-S3-v2-20261103',
- 'purpose': 'Unsaved S3 v2 template: display all eight existing pending questions',
- 'sessionIdInTemplate': None, 'taskKey': v2['taskKey'], 'version': 2,
- 'stageCode': 'S3', 'artifactType': v2['artifactType'],
- 'priorArtifactId': 'oiNxX7o27j',
- 'priorTemplateSha256': sha(v1_bytes), 'templateSha256': sha(v2_bytes),
- 'changedPaths': ['version', 'report.chapters[2].sections[3] (append table)'],
- 'rawTextUnchanged': True, 'sourceIds': v2['sourceIds'],
- 'pendingQuestionCount': 8, 'pendingQuestionsInDisplayTable': 8,
- 'evidenceKeys': list(v2['report']['evidence']),
- 'opportunityObjectPresent': False, 'projectIdPresent': False,
- 'templatePreviewVerified': True, 'browserVerified': False, 'businessWrites': 0,
- 'files': {name: {'bytes': len(data), 'sha256': sha(data)} for name, data in payload.items()},
- }
- payload['MANIFEST.json'] = as_json(manifest)
- checksums = ''.join(sha(payload[name]) + ' ' + name + '\n' for name in sorted(payload))
- payload['FILES.sha256'] = checksums.encode('utf-8')
- assert len(payload) == 7
- STAGE.mkdir()
- for name, data in payload.items():
- write_new(STAGE / name, data)
- with zipfile.ZipFile(DELIVERY, 'x', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as package:
- for name in sorted(payload):
- package.write(STAGE / name, arcname=name)
- with zipfile.ZipFile(DELIVERY) as package:
- assert sorted(package.namelist()) == sorted(payload)
- for name, data in payload.items():
- assert package.read(name) == data
- assert not name.startswith(('/', '\\')) and '..' not in Path(name).parts
- assert package.testzip() is None
- for line in checksums.splitlines():
- expected_sha, name = line.split(' ', 1)
- assert sha(package.read(name)) == expected_sha
- result = {
- 'ok': True, 'operation': 'package-unsaved-S3-v2-display-fix',
- 'packagePath': str(DELIVERY), 'packageBytes': DELIVERY.stat().st_size,
- 'packageSha256': sha(DELIVERY.read_bytes()), 'entries': len(payload), 'fileChecks': '6/6',
- 'templateBytes': len(v2_bytes), 'templateSha256': sha(v2_bytes),
- 'templatePreviewVerified': True, 'browserVerified': False,
- 'businessWrites': 0,
- }
- write_new(RECEIPT, as_json(result))
- print(json.dumps(result, ensure_ascii=True, indent=2))
|