package-industry-access-log-review.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """Archive the ledger sidecar review without re-exporting private operator data."""
  2. import hashlib
  3. import json
  4. from pathlib import Path
  5. import zipfile
  6. ROOT = Path(__file__).resolve().parents[1]
  7. WORK = ROOT/'work/industry-access-log-receipt-review-20261107-r1'
  8. OUT = ROOT/'artifacts/ksf-industry-access-log-review-20261107-r1'
  9. def sha(raw):
  10. return hashlib.sha256(raw).hexdigest()
  11. def clean(value):
  12. if isinstance(value, dict):
  13. return {k: clean(v) for k, v in value.items()}
  14. if isinstance(value, list):
  15. return [clean(v) for v in value]
  16. if isinstance(value, str):
  17. normalized = value.replace('\\', '/')
  18. prefix = str(ROOT).replace('\\', '/')+'/'
  19. if normalized.startswith(prefix):
  20. return 'workspace/'+normalized[len(prefix):]
  21. if normalized.startswith('D:/下载/'):
  22. return 'local-input/'+normalized.rsplit('/', 1)[1]
  23. return value
  24. def encoded(value):
  25. return (json.dumps(clean(value), ensure_ascii=False, indent=2)+'\n').encode('utf-8')
  26. def main():
  27. sidecar = json.loads((WORK/'parameter-sidecar.json').read_bytes())
  28. plan = json.loads((WORK/'per-product-page-plan.json').read_bytes())
  29. proof = json.loads((WORK/'verification.json').read_bytes())
  30. assert proof['operatorReportedAccessLogParams'] == 9 and proof['paramsStillUnknown'] == 2
  31. table = '\n'.join('| '+r['ledgerId']+' | '+r['endpointId']+' | `'+
  32. json.dumps(r['effectiveParamsSidecar'], ensure_ascii=False)+'` | '+
  33. ('运维访问日志回报' if r['state'] == 'OPERATOR_REPORTED_ACCESS_LOG_PARAMS' else
  34. '旧回执明确声明' if r['state'] == 'PRIOR_OPERATOR_RECEIPT_DECLARED_PARAMS' else '未知')+' |'
  35. for r in sidecar['rows'])
  36. candidate_table = '\n'.join('| '+r['ledgerId']+' | '+Path(c['existingFile']).name+' | '+
  37. str(c['bytes'])+' | '+('参数与体积相同' if c['evidence'] == 'PARAMS_AND_BYTE_COUNT_MATCH'
  38. else '返回店铺与体积相同')+' |' for r in sidecar['rows']
  39. for c in r.get('existingResponseCandidates', []))
  40. report = f'''# 旧请求定位回传核对:参数旁表与正文状态分开
  41. 固定代际 `{proof['generation']}`。本轮核验附件与已有冻结资料,未直接连接发布机日志或生产账本。
  42. ## 附件及字段核对
  43. 来包4,007 B,SHA-256 `{proof['archiveSha256']}`,与用户回报一致;3条目无重复名,清单2/2内容文件通过。JSON内11条完整requestId、ledgerId、usageTraceId、接口版本及cache/upstream状态,与冻结账本逐件一致;9条Markdown声明的日志时间和响应字节数与JSON、旧账本时钟文本相符,2条保持参数未知。
  44. 回传内没有原始访问日志行或其行号/行哈希,JSON的requestTimeOriginalLedger均为空。9条参数以“运维依据真实访问日志回报”登记于旁表;本机没有把回报改写成直接读取原日志,也没有从请求名推导参数。时间比较仅核文字相符,不额外推断时区或原响应观察时间。
  45. ## 15条原参数未知行的现状
  46. | 账本ID | 既有接口 | 旁表参数 | 证据状态 |
  47. | --- | --- | --- | --- |
  48. {table}
  49. 合计9条日志回报、4条此前回执声明、2条未知。冻结账本154条/¥46.20及其中15行params=null原样保留;没有覆盖旧记录或补写生产资料。
  50. 757123支持的是已追溯商品 `jd:100005702440` 第1页,该商品已有19条库内评价。它不属于58件“本次未找到评价原件”商品,因此这次参数恢复不会增加那些商品的覆盖,也不能把50个历史候选改算成确定补采量。
  51. 757120、757127继续未知。访问日志无行只是在所查范围未找到,不证明未收费或调用未发生;两条均已在账本。尤其757127不能按名称推定page=2,不能据已知的旧c2.json给它补上参数。
  52. ## 本地旧原响应候选:6份参数与体积相同,另1份仅返回店铺与体积相同
  53. 复核既有hand-off原件时,找到以下候选;逐件SHA和上游requestId、recordTime保留在JSON。它们本来就在本地,不是本轮新取回的收费正文:
  54. | 对应收费行线索 | 已有文件 | 字节 | 比较结果 |
  55. | --- | --- | ---: | --- |
  56. {candidate_table}
  57. 访问日志回报没有正文SHA、原始日志请求ID关联或providerRequestId,因此参数和体积一致仍不能证明是同一笔收费响应。h1.json缺requestParams,只有返回shopId和体积相同,证据更弱。候选中的旧recordTime不得替代收费请求时间,也不能伪装成新采集。
  58. 本轮该11笔对应的新正文取得数仍为0;此前另8笔已收费无正文也未找回。两批缺正文不混为同一分母。原件可用于其已有历史来源核对,但暂不据此结清收费请求与正文关联。
  59. ## 本轮收口与主线影响
  60. 当前对账先收口,757120/757127留作未解决项;无需仅为这两项继续扩大到HAProxy或应用日志,也不为了补齐账目重放收费请求。若将来随维护自然取得旧日志,可以追加旁证;它们不阻塞政策/膳食条款、榜单能力与其他主线资料工作。
  61. 165件逐商品计划和9+4+2参数旁表已归档。产品状态不变:47件登记响应、31件其他来源核对通过、1件现库归属冲突、13件待归属冲突、8件已收费缺正文、7件只见他品返回、58件所查范围无评价原件。50项仍仅为历史第2页候选,exactNewRequestCount=null、预算上界=null、dispatchAllowed=false。244页/¥73.20估算仍不采用。
  62. 随回报出现的3个远端辅助脚本写入失败只作为运维报告的失败痕迹;本轮未执行它们,也不拿这些脚本证明成功查询或变更已落地。本机未访问远端文件系统,不能用本机git状态代替远端落盘检查。附件哈希与回传字段的核对结果独立保留。
  63. 收费继续停止:已扣¥46.20、未决¥0、保留¥3.80、本轮新增付费0、研究数据库写入0。A/B/C、八店v3、机会排序、线上服务/worker/UI不变。完整采集、完整榜单及持续监听仍未全部完成。
  64. 本包不含账号标签、余额、凭据、请求头或原响应正文;是审计归档,不需要部署或要求运维重做本轮取件。
  65. '''
  66. assert not OUT.exists(), 'OUTPUT_ALREADY_EXISTS'
  67. files = {'README.md': report.encode('utf-8'),
  68. 'evidence/parameter-sidecar.json': encoded(sidecar),
  69. 'evidence/per-product-page-plan.json': encoded(plan),
  70. 'evidence/verification.json': encoded(proof),
  71. 'scripts/reconcile-industry-access-log-receipt.py': (ROOT/'scripts/reconcile-industry-access-log-receipt.py').read_bytes()}
  72. files['FILES.sha256'] = ''.join(sha(body)+' '+name+'\n' for name, body in sorted(files.items())).encode('utf-8')
  73. OUT.mkdir(parents=True)
  74. for name, body in files.items():
  75. target = OUT/'package'/name
  76. target.parent.mkdir(parents=True, exist_ok=True)
  77. target.write_bytes(body)
  78. archive = OUT/'ksf-industry-access-log-review-20261107-r1.zip'
  79. with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED, compresslevel=9) as z:
  80. for name, body in sorted(files.items()):
  81. z.writestr(name, body)
  82. with zipfile.ZipFile(archive) as z:
  83. assert len(z.namelist()) == len(set(z.namelist())) == len(files)
  84. for name, body in files.items():
  85. assert z.read(name) == body
  86. receipt = {'ok': True, 'archive': str(archive), 'bytes': archive.stat().st_size,
  87. 'sha256': sha(archive.read_bytes()), 'manifestFilesVerified': len(files)-1,
  88. 'operatorReportedParams': 9, 'priorReceiptDeclaredParams': 4, 'unknownParams': 2,
  89. 'responseBodiesNewlyRecovered': 0, 'frozenLedgerModified': False,
  90. 'productionModified': False, 'paidCalls': 0}
  91. (OUT/'receipt.json').write_bytes(encoded(receipt))
  92. (ROOT/'docs/industry-monitoring-20261107/r11旧请求参数回传核对收口.md').write_text(report, encoding='utf-8', newline='\n')
  93. print(json.dumps(receipt, ensure_ascii=False))
  94. if __name__ == '__main__':
  95. main()