update-S5-acceptance.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. """Record the independent S5 acceptance locally; never call business APIs."""
  2. from datetime import datetime, timezone
  3. from hashlib import sha256
  4. import json
  5. from pathlib import Path
  6. ROOT = Path('F:/kangshifu-board')
  7. DOCS = Path('F:/文档/工作坊当前资料')
  8. WORK = ROOT / 'work'
  9. BACKUP = WORK / 'S5-progress-before-acceptance-20261103'
  10. RECEIPT = WORK / 'live-S5-independent-acceptance-20261103.json'
  11. REPORT = DOCS / '55-S5方案与课堂报告独立验收结果.md'
  12. def digest(path):
  13. return sha256(path.read_bytes()).hexdigest()
  14. data_path = WORK / 'live-S5-readback-20261103-02.json'
  15. browser_path = WORK / 'live-S5-browser-verified-20261103.json'
  16. data = json.loads(data_path.read_text(encoding='utf-8'))
  17. browser = json.loads(browser_path.read_text(encoding='utf-8'))
  18. assert data['ok'] and data['checksPassed'] == 14
  19. assert browser['ok'] and browser['browserVerified']
  20. assert len(browser['checks']) == 235 and all(c['passed'] for c in browser['checks'])
  21. assert not browser['errors']
  22. assert data['executionConformsToFixedRequestIds'] is False
  23. assert data['requestIdDeviation']['expectedKeyCommitted'] is False
  24. assert data['artifactId'] == browser['artifactId'] == 'tLkz2MAoIw'
  25. assert data['conceptId'] == browser['conceptId'] == 'QO0uo8nisk'
  26. immutable = {
  27. ROOT / 'work/S5-classroom-next-20261103/01-project-S5-progress.template.json': '049251b5c1d1984cae72c216ac025e9eb68bd87d02de16166cc7dd4f19bce213',
  28. ROOT / 'work/S5-classroom-next-20261103/02-concept-save.template.json': '5c182b5c5733218577bca330df90b85b91d69ad1b6b1e5494bab5b06e2238e4d',
  29. ROOT / 'work/S5-classroom-next-20261103/03-project-S5-save.template.json': 'f8dc7d8a9f0b2a998f90453add9ca02f34a60cfc52be3bc592deab87d49ada8c',
  30. DOCS / 'ksf-S5-classroom-next-20261103.zip': '70039fcbecd3bd2bef9f36659c8a55c270f8472e57ea7b6488c8a9f8ff5f5ff1',
  31. }
  32. for path, expected in immutable.items():
  33. assert digest(path) == expected, path
  34. for name in ['52-S5桌面方案审阅与定稿.md', '53-发给运维001-S5方案与报告准备.txt', '54-发给王刚001-S5方案与课堂报告保存.txt']:
  35. path = DOCS / name
  36. immutable[path] = digest(path)
  37. assert not BACKUP.exists() and not RECEIPT.exists() and not REPORT.exists()
  38. originals = {name: (DOCS / name).read_bytes() for name in ['01-工作进展汇报.txt', '02-后续待补资料.md', 'README.md']}
  39. visual_files = [
  40. 'work/live-S5-project-desktop-1793683536092.png',
  41. 'work/live-S5-table-0-desktop-1793683536092.png',
  42. 'work/live-S5-project-mobile-1793683536092.png',
  43. 'work/live-S5-table-0-mobile-1793683536092.png',
  44. 'work/live-S5-table-4-mobile-1793683536092.png',
  45. 'work/live-S5-evidence-public-p1-mobile-1793683536092.png',
  46. ]
  47. for path in browser['screenshots']:
  48. assert (ROOT / path).is_file()
  49. receipt = {
  50. 'at': datetime.now(timezone.utc).isoformat(),
  51. 'operation': 'live-S5-independent-acceptance',
  52. 'contentAndPageAccepted': True,
  53. 'executionConformsToFixedRequestIds': False,
  54. 'requestIdDeviation': data['requestIdDeviation'],
  55. 'readOnly': True,
  56. 'businessWrites': 0,
  57. 'sessionId': data['sessionId'],
  58. 'opportunityId': data['opportunityId'],
  59. 'projectId': data['projectId'],
  60. 'conceptId': data['conceptId'],
  61. 'artifactId': data['artifactId'],
  62. 'outputSourceId': data['outputSourceId'],
  63. 'reportUrl': data['reportUrl'],
  64. 'stageCode': 'S5',
  65. 'progressState': 'in_progress',
  66. 'checks': {'data': 14, 'productionBrowser': 235},
  67. 'browserVerified': True,
  68. 'browserVerificationStoredLocallyOnly': True,
  69. 'viewports': browser['viewports'],
  70. 'visualReview': {'performed': True, 'screenshots': visual_files},
  71. 'reportTables': data['reportTables'],
  72. 'rawTextCodepoints': 3655,
  73. 'chapterCount': 5,
  74. 'sectionCount': 15,
  75. 'tableCount': 5,
  76. 'sourceIds': data['sourceIds'],
  77. 'evidenceIds': data['evidenceIds'],
  78. 'pendingQuestionCount': 13,
  79. 'projectArtifactIds': data['projectArtifactIds'],
  80. 'projectEvents': data['projectEvents'],
  81. 'committedWriteCount': 3,
  82. 'committedWriteRequests': data['committedWriteRequests'],
  83. 'runbookReadCommandCount': 5,
  84. 'countsBasis': 'Three committed write commands verified from operation audit; five read commands specified by the eight-step runbook. Remote CLI execution counts, exit codes and invocation IDs are operator-reported.',
  85. 'oldReportsPreserved': True,
  86. 'formalReportPublished': False,
  87. 'employeeConfirmed': False,
  88. 'enterpriseApproval': False,
  89. 'researchValidationPerformed': False,
  90. 'fieldLocations': data['fieldLocations'],
  91. 'retainedDisplayObservations': [
  92. '390px four-column tables need substantial vertical scrolling; all cells remain reachable.',
  93. 'Retailer listing drawer retains the generic unknown-person-count label; the dash means unknown, not one consumer.',
  94. 'Long source locator in the mobile drawer needs inner horizontal scrolling; no page-level overflow.',
  95. ],
  96. 'remoteFilesystemIndependentlyVerified': False,
  97. 'initialStrictAuditRetained': str(WORK / 'live-S5-readback-20261103.json'),
  98. 'auditInspectionRetained': str(WORK / 'S5-write-audit-inspection-20261103.json'),
  99. 'evidenceFiles': [
  100. {'path': str(p), 'sha256': digest(p)}
  101. for p in [data_path, browser_path, WORK / 'S5-write-audit-inspection-20261103.json', WORK / 'live-S5-readback-20261103.json']
  102. ],
  103. 'screenshots': [{'path': str(ROOT / p), 'sha256': digest(ROOT / p)} for p in browser['screenshots']],
  104. 'immutableFiles': [{'path': str(p), 'sha256': h} for p, h in immutable.items()],
  105. 'documentationBackup': str(BACKUP),
  106. }
  107. report = '''# S5方案与课堂报告独立验收结果
  108. 2026年11月3日。Codex独立核对生产数据及匿名电脑、手机页面后,确认S5概念、报告内容与项目关联通过验收。执行过程存在一处报告保存requestId偏差,必须保留,不能表述为“全部按54号原业务键完成”。本轮仅只读验收及本地文件整理,业务写入0次。
  109. | 项目 | 独立核实结果 |
  110. |---|---|
  111. | 方向 | 燃魂份量与用餐目的适配,沿用用户选择 |
  112. | 会话 / 机会卡 | btrFqkz0yw / QE9hjtCj8E;机会仍为假设,课堂approved不代表企业批准 |
  113. | 项目 | S_CikxZeBs,S5 / in_progress,dataMode=live |
  114. | 概念 | QO0uo8nisk,version=1,燃魂份量选择提示与搭配指引(待验证概念) |
  115. | S5报告 / 正文来源 | tLkz2MAoIw / 5zsPtYFL1u;version=1 |
  116. | 报告类型 | artifactType=concept;reportType=workshop_auxiliary;artifactSubtype=classroom_task |
  117. | 系列 / 标签 | KSF-真实资料-20261102-01:S5;拌面、课堂过程成果 |
  118. | 数据检查 | 14项通过,正文3655码点、概念内容、4来源/4引文、13待补问题与固定模板一致 |
  119. | 生产页面 | 1440px电脑、390px手机,235项功能检查通过;22张截图,6张代表截图目视复核 |
  120. | 项目成果 | 恰3份:XF4h-Z7l5o(S3)、U3_W7WDliP(S4)、tLkz2MAoIw(S5) |
  121. | 当前方案 | currentConceptRef→KfConcept/QO0uo8nisk;同项目概念恰1个,无配方条目 |
  122. | 仍未发生 | 课堂完成、员工确认、正式发布、企业批准、实物/市场/量产验证 |
  123. 报告:[燃魂份量选择提示与搭配指引|S5桌面方案 第1版](https://kangshifu.fmode.cn/report/tLkz2MAoIw)。
  124. ## 执行键偏差及处置
  125. | 第06步报告保存 | 完整requestId |
  126. |---|---|
  127. | 54号规定 | KSF-真实资料-20261102-01:S5:save:v1 |
  128. | 生产实际成功 | KSF-真实资料-20261102-01:portion-fit-project:S5:save:v1 |
  129. 独立读取KsfOperation成功审计及对应回执,确认实际键关联报告tLkz2MAoIw和正文5zsPtYFL1u;规定键没有成功提交审计。其余两个写入键与执行单一致。reportSeriesKey仍正确为KSF-真实资料-20261102-01:S5,不能把requestId与报告系列键混为一谈。
  130. 首次严格验收在此项停止,原失败记录保留;随后按已返回业务编号定位实际审计,再进行内容和页面只读验收。执行键为什么变化尚未查明,Codex未取得远端命令和原始日志,不作归因。以后交接沿用实际成功键与业务编号;**不得用尚未使用的规定键补跑保存,不重存、不换键、不删记录,不回改54号或原包来掩盖偏差。** 只需下次交接附上实际第06步命令与回执用于溯源,不影响现有报告使用。
  131. S5本轮实测恰3条成功写入审计:workshop-transition(转S5)、workshop-concept(保存概念)、workshop-task(保存报告)。对应3个写命令,事件属于这些命令的副作用;不能另计为第四个写命令。8步执行单由3个写命令和5个读命令组成。读命令的实际次数、退出码、stderr、两层invocationId和远端29个文件权限仍依据王刚001回报,Codex没有独立读取远端文件系统,也不用写审计证明只读命令从未运行。
  132. ## 内容及字段核对
  133. 概念九项内容字段及specification九个子字段与模板相同。netWeightG/noodleWeightG/packCount/currentSku等未知项保留null,formulaItems与productionSteps为空;没有把P1图示克重当新品规格,没有补造配方、成本或工艺。KfConcept.reviewState=pending、proposalArtifactRef=null,不声称方案已正式批准或已建立报告Pointer。
  134. 字段落点符合当前服务实现:concept输入的13条pendingQuestions保存在项目meta.pendingQuestions及保存回执,报告另有meta.workshop.pendingQuestions;KfConcept本身没有同名键。输入taskKey写入reportSeriesKey,不要求记录出现taskKey键。project-status的formulaItems为响应顶层数组,不要求项目记录出现同名字段;独立配方条目数为0。这些不作为数据丢失,也不补写旧记录。
  135. 报告实际5章15节、5张表,修正回报中的“13节/4个table”说法:
  136. | 表 | 列×行 |
  137. |---|---|
  138. | 设计对照 | 4×4 |
  139. | 设计、观察与未知 | 3×8 |
  140. | 主引文 | 3×4 |
  141. | 继续、调整与暂停 | 4×4 |
  142. | 待补问题 | 3×13 |
  143. 13个待补问题逐字等于模板;前8条沿用S4,后5条为设计问题。可见表第三列前8行是“原S4问题”,后5行分别为“承原1、4”“承原4、6”“承原7”“承原8”“承原3、6”,不写成统一“新增设计问题”。executive仅由服务端前置课堂声明,report.title/generatedAt属于预期服务补充。
  144. 四条来源及证据逐字核对,编号保留下划线:
  145. | 用途 | sourceId | evidenceId |
  146. |---|---|---|
  147. | cq-03历史份量自述 | m1OQx9aynA | _9mUml0wmh |
  148. | cq-04历史份量自述 | QdeMcyTTjX | 2bFGP7axMo |
  149. | P1苏宁公开图片页观察 | PjORcvaRHn | xHGV_mRWcG |
  150. | P2历史个人煮制叙述 | 5S_Y4IgT5q | CeAEIwymTa |
  151. 四条productRef均为空,不合并同SKU或同批次,不认定当前在售、真实购买身份或普遍需求。P1不证明信息分散,P2不证明包装错误;信息布局与可选搭配仍是待验证设计,不能称已验证卖点。
  152. ## 项目及页面
  153. 项目恰7条有序事件,没有completed事件:
  154. | 序号 | eventId | eventType |
  155. |---|---|---|
  156. | 1 | GGDTcg5YTy | project_created |
  157. | 2 | RkkTTE_xeb | task_submitted(S3) |
  158. | 3 | hOFghbcMsY | stage_changed(S4) |
  159. | 4 | VlGFu_jv4b | task_submitted(S4) |
  160. | 5 | 0ya0a03U9G | stage_changed(S5) |
  161. | 6 | qKnPnVkZA8 | concept_saved |
  162. | 7 | iUs3JwZ9Kf | task_submitted(S5) |
  163. 同工作坊现有9份报告,S5系列只有第1版;没有第二套机会卡、项目或概念。旧S1—S4正文、结构与原有待补问题集仍保持原样,当前项目13条未决项不覆盖旧报告的8条。
  164. 匿名生产浏览器核实了成员项目行、阶段页、报告列表及报告详情。成员项目行的模型/桌面显示对应S5、3份成果、方案已保存、进行中;手机折叠行展示项目名和S5进行中,不把隐藏列写成全在首屏可见。两种屏宽逐表对照所有行列,滚动检查可达性,真实点击4条引文、核逐字文本和原来源,Escape关闭。引文比较保留原换行,仅忽略显示插入的U+2009细空格。未发现页面级横向溢出、运行或网络错误,旧报告仍可读。
  165. 保留显示改进:390px四列表格列窄、需较多纵向滚动;商品页证据抽屉仍沿用“—人独立提到”的人数未知标签(不是1人);手机抽屉长出处需内部横滚。来源内容可达,功能通过;本轮未改前端,也不声称这些显示问题已经修复。
  166. boardVisible=true、formalReportPublished=false、employeeConfirmed=false由实际成功保存审计核实。dashboardState=workshop不等于正式发布。browserVerified=true仅记录在本地Codex验收证据,未回写生产。enterpriseApproval=false。
  167. 本次交付完成S5保存与只读独立验收;当前仍S5/in_progress。下一步可整理课堂总结和完成条件,另行执行对应状态操作;本轮不自动completed、不代员工正式确认或发布。实物研究需要时再明确样品、资源和方法,不要求用户先补实物才能继续软件流程。
  168. 本地证据(均不含凭据或浏览器档案):
  169. - F:/kangshifu-board/work/live-S5-independent-acceptance-20261103.json
  170. - F:/kangshifu-board/work/live-S5-readback-20261103.json(首次严格验收失败,保留)
  171. - F:/kangshifu-board/work/S5-write-audit-inspection-20261103.json(实际成功键定位)
  172. - F:/kangshifu-board/work/live-S5-readback-20261103-02.json(14项数据核对)
  173. - F:/kangshifu-board/work/live-S5-browser-verified-20261103.json(235项及22张截图索引)
  174. 01、02及README更新前完整快照保留在F:/kangshifu-board/work/S5-progress-before-acceptance-20261103/。52/53/54号、三个固定模板、原S5包及所有旧回执保留原样。
  175. '''
  176. progress = '''工作坊进展(截至2026年11月3日,最新独立验收见55号)
  177. 真实资料课堂流程已推进到S5 / in_progress,选定方向为“燃魂份量与用餐目的适配”。概念QO0uo8nisk和S5报告tLkz2MAoIw均已保存,Codex独立数据核对14项、电脑/手机生产浏览器检查235项通过,并已目视复核代表截图。课堂仍进行中,未标完成、未员工确认、未正式发布、未企业批准;实物、市场和量产验证均未做。
  178. 会话btrFqkz0yw;课堂机会卡QE9hjtCj8E;项目S_CikxZeBs。项目恰3份成果:S3基线XF4h-Z7l5o、S4评估U3_W7WDliP、S5方案tLkz2MAoIw;当前概念1个、事件7条。报告正文3655码点,5章15节5张表,4来源/4引文、13待补问题与固定模板一致,旧S1—S4保持原样。
  179. 必须保留的执行偏差:54号规定S5报告保存requestId为KSF-真实资料-20261102-01:S5:save:v1,实际成功键为KSF-真实资料-20261102-01:portion-fit-project:S5:save:v1。实际键与报告/正文编号已核实;规定键没有成功提交审计。不得用规定键补跑保存,不重存、不换键、不删记录,也不回改原执行单掩盖差异。原因尚未查明;下次交接附实际第06步命令与原回执即可,不必重跑业务。
  180. 本轮S5成功写命令是3个(转阶段、保存概念、保存报告);8步执行单另有5个读命令,事件为写命令副作用,不另计第4个写命令。远端文件数量/权限、CLI退出码和invocationId依据王刚001回报,Codex独立确认的是生产数据、成功写入审计与匿名页面。
  181. 方案是信息呈现与可选搭配设计,尚未确定新品规格。净含量、面饼重量、件数和当前SKU等未知值保留null,配方/工艺为空。历史帖子仅证明个体自述,P1公开图片页仅证明页面/图示展示;不合并同SKU、不证明当前在售或普遍需求。13待补含原S4八项和新增五项设计问题。
  182. 保留显示改进:手机四列表格较窄、需纵向滚动;商品页抽屉仍用人数未知标签;手机长出处需抽屉内横滚。功能可用,本轮未改前端。browserVerified=true仅为本地验收记录,未回写生产。
  183. 下一步可整理课堂总结与完成条件,再执行相应状态操作;本次没有自动completed。真实研究缺口继续保留,不要求用户先供实物才能继续软件课堂流程。员工实名绑定、新数字人接入及旧机器人密钥轮换仍待处理。
  184. 最新S5报告:https://kangshifu.fmode.cn/report/tLkz2MAoIw
  185. S4桌面评估:https://kangshifu.fmode.cn/report/U3_W7WDliP
  186. 看板:https://kangshifu.fmode.cn/
  187. 详细验收:55-S5方案与课堂报告独立验收结果.md。
  188. 更新前汇报快照:F:/kangshifu-board/work/S5-progress-before-acceptance-20261103/。
  189. S5固定材料包及52/53/54号保留原样,不再次执行。
  190. '''
  191. needs = originals['02-后续待补资料.md'].decode('utf-8-sig')
  192. old_intro = '2026年11月3日更新。课堂系统当前S4 / in_progress;S4报告U3_W7WDliP的数据与电脑/手机功能已独立验收,详见50号。下一步可写S5桌面方案草稿;下面研究缺口不作为全部软件流程测试的统一前置,不要求用户现在提供原件,也不重查已知登录墙。'
  193. assert old_intro in needs
  194. needs = needs.replace(old_intro, '2026年11月3日更新。课堂系统当前S5 / in_progress;概念QO0uo8nisk和S5报告tLkz2MAoIw已保存,数据14项、电脑/手机生产浏览器235项检查通过,详见55号。报告保存键存在已留档偏差,不补跑保存。下面研究缺口继续保留,不作为全部软件流程测试的统一前置,不要求用户现在提供原件,也不重查已知登录墙。', 1)
  195. start = needs.index('软件流程下一步:')
  196. end = needs.index('\n\n前端两项独立待改:', start)
  197. needs = needs[:start] + '''软件流程状态:S5保存与独立验收完成,项目恰3报告、1概念、7事件;仍S5/in_progress,尚未课堂完成、确认或发布。当前方案未知规格为null、formulaItems/productionSteps为空。13条未决问题中前8条沿用S4,新增5条设计问题如下:
  198. 1. 净含量/面饼/件数三行分区能否正确理解,会不会更乱。
  199. 2. 信息呈现是否增加货架识别、比价或停留负担。
  200. 3. 包材版面和渠道素材能否执行,实际成本未知。
  201. 4. 可选搭配会不会被读成必须添加,反向暗示份量不足。
  202. 5. 记录补主食原因会不会影响行为,记录负担和观察者效应待评估。
  203. 下一步可整理课堂总结与完成条件;不重跑53/54号,不补存新版本。第06步实际成功requestId为KSF-真实资料-20261102-01:portion-fit-project:S5:save:v1,与54号规定的KSF-真实资料-20261102-01:S5:save:v1有差异。实际报告已存在,严禁用规定键补跑;只需下次交接附原命令与回执说明原因,不要求重新执行。员工正式确认和发布另走各自流程。''' + needs[end:]
  204. needs = needs.replace('前端两项独立待改:商品页证据抽屉的人数标签、390px长出处自动换行。功能验收通过,本轮未改前端。', '前端独立待改:商品页证据抽屉的人数未知标签、390px长出处自动换行,以及窄屏四列表格阅读体验。功能验收通过,本轮未改前端。')
  205. needs = needs.replace('更新前资料清单完整快照位于F:/kangshifu-board/work/S4-progress-before-acceptance-20261103/。', '本轮更新前完整快照位于F:/kangshifu-board/work/S5-progress-before-acceptance-20261103/;此前S4快照仍保留。')
  206. readme = originals['README.md'].decode('utf-8-sig')
  207. start = readme.index('当前状态(')
  208. end = readme.index('\n\n以下编号材料', start)
  209. readme = readme[:start] + '当前状态(2026年11月3日):S5 / in_progress。概念QO0uo8nisk、报告tLkz2MAoIw已保存;Codex14项数据和235项匿名电脑/手机生产浏览器检查通过,详见55号。项目3报告、1概念、7事件,未课堂完成、员工确认或正式发布,未做实物/市场/量产验证。第06步实际报告保存键较54号多了:portion-fit-project,偏差已留档;不得用规定键补跑保存。52/53/54及原包保留,不重跑。下一步可整理课堂总结和完成条件。' + readme[end:]
  210. readme = readme.replace('旧01/02/README完整快照在F:/kangshifu-board/work/S4-progress-before-acceptance-20261103/。', '本轮旧01/02/README完整快照在F:/kangshifu-board/work/S5-progress-before-acceptance-20261103/;S4旧快照仍保留。', 1)
  211. readme = readme.replace('- 51-发给王刚001-S5桌面方案草稿.txt:', '- 55-S5方案与课堂报告独立验收结果.md:当前结论;内容和生产页面通过,保存requestId偏差、计数及字段落点已校正,保持S5/in_progress,不补跑保存。\n- 51-发给王刚001-S5桌面方案草稿.txt:', 1)
  212. readme = readme.replace('- 52-S5桌面方案审阅与定稿.md:当前审阅结果;', '- 52-S5桌面方案审阅与定稿.md:历史审阅结果;', 1)
  213. readme = readme.replace('- 53-发给运维001-S5方案与报告准备.txt:当前下一步;原样落地3模板并只读核合同、归属、来源、历史、在途。', '- 53-发给运维001-S5方案与报告准备.txt:已回报落地预检完成,原单保留,不重跑准备。', 1)
  214. readme = readme.replace('- 54-发给王刚001-S5方案与课堂报告保存.txt:运维预检通过后在真实消息执行8步;project-progress→concept-save→workshop-save,最终S5/in_progress,不自动completed。', '- 54-发给王刚001-S5方案与课堂报告保存.txt:已执行并保存S5;第06步实际成功requestId与原单不同,见55号。保留原单不回改,不补跑规定键,不自动completed。', 1)
  215. readme = readme.replace('- S5桌面方案第1版-审阅稿.md:拟保存的正文审阅稿,尚未保存生产。', '- S5桌面方案第1版-审阅稿.md:已保存内容的审阅稿,生产报告tLkz2MAoIw及回读为准。', 1)
  216. readme = readme.replace('- ksf-S5-classroom-next-20261103.zip:3固定模板、runbook、审阅及交接;11条目、10/10清单核验。sessionId/projectId为null,不可直接提交。', '- ksf-S5-classroom-next-20261103.zip:已使用的3模板与交接原包,11条目、10/10核验;指纹未变,不再次执行。ID绑定为null,不可直接提交。', 1)
  217. readme = readme.replace('- 50-S4课堂桌面评估独立验收结果.md:当前结论;', '- 50-S4课堂桌面评估独立验收结果.md:S4阶段历史结论;', 1)
  218. readme = readme.replace('当前入口:\n\n', '当前入口:\n\n- 最新S5桌面方案:https://kangshifu.fmode.cn/report/tLkz2MAoIw\n', 1)
  219. readme = readme.replace('- 最新S4桌面评估:', '- S4桌面评估:', 1)
  220. updates = {'01-工作进展汇报.txt': progress, '02-后续待补资料.md': needs, 'README.md': readme}
  221. for name, text in updates.items():
  222. assert 'S5 / in_progress' in text
  223. assert 'tLkz2MAoIw' in text
  224. assert 'portion-fit-project:S5:save:v1' in text or name == 'README.md'
  225. assert '尚未保存concept或S5报告' not in readme
  226. assert '当前下一步;原样落地3模板' not in readme
  227. BACKUP.mkdir()
  228. for name, content in originals.items():
  229. with (BACKUP / name).open('xb') as stream:
  230. stream.write(content)
  231. with (BACKUP / 'manifest.json').open('x', encoding='utf-8') as stream:
  232. json.dump({name: sha256(content).hexdigest() for name, content in originals.items()}, stream, ensure_ascii=False, indent=2)
  233. with RECEIPT.open('x', encoding='utf-8') as stream:
  234. json.dump(receipt, stream, ensure_ascii=False, indent=2)
  235. with REPORT.open('x', encoding='utf-8') as stream:
  236. stream.write(report)
  237. for name, content in updates.items():
  238. (DOCS / name).write_text(content, encoding='utf-8')
  239. for path, expected in immutable.items():
  240. assert digest(path) == expected, path
  241. for name, content in originals.items():
  242. assert (BACKUP / name).read_bytes() == content
  243. print(json.dumps({'ok': True, 'businessWrites': 0, 'acceptance': str(REPORT), 'receipt': str(RECEIPT), 'backup': str(BACKUP), 'updated': list(updates), 'immutableFilesUnchanged': len(immutable)}, ensure_ascii=False, indent=2))