| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- """Seal the r10 workshop manual, including a standalone preview and deployment instructions."""
- import argparse
- import hashlib
- import json
- from pathlib import Path
- import zipfile
- ROOT = Path(__file__).resolve().parents[1]
- SOURCE = ROOT / 'docs/workshop-manual-20261109'
- OUT = ROOT / 'docs/workshop-manual-20261110-r1'
- RELEASE = 'ksf-workshop-manual-20261110-r1'
- NAMES = ['index.html', 'content.js', 'manual.js', 'manual.css']
- sha = lambda b: hashlib.sha256(b).hexdigest()
- encode = lambda v: (json.dumps(v, ensure_ascii=False, indent=2) + '\n').encode('utf-8')
- DEPLOY = '''# 康师傅工作坊 r10 · 联合上线执行单
- 本执行单只更新工作坊技能说明与静态操作手册;数据库迁移、后端运行补丁已经验收,不重跑迁移或重启数据服务。
- ## 资源
- 以本执行单所在的固定 Git 提交目录为根目录,读取 manual-release.json;下载其 archive.path,核字节数及 SHA-256。在新临时目录安全解压并逐项核 FILES.sha256,不覆盖未知目录或改用分支最新地址。包内 manual/ 是四文件站点,ksf-workshop-manual-r10.html 是同内容的单文件预览。
- 配套技能固定安装说明:
- {guide}
- 该说明对应 Skill r10、唯一内部入口 ksf-workshop-runtime,支持完整 r7/r8/r9 备份升级,外部官方 VOC 固定0.3.42。技能与手册联合版本均为r10。
- ## 1. 更新原工作坊Agent技能
- 在原Agent环境按上述安装说明核包、预检、备份升级,再验证43文件。使用已确认的实际技能根,不覆盖身份、授权、绑定及额外文件,不改其他技能。六个业务脚本与runtime-lock.json共7文件必须保持原指纹。保留已退役的旧研究目录为脚本兼容路径,不恢复其SKILL.md;已部署研究运行补丁保持原样。
- 同版只核验;未知版本或已改动说明停止覆盖并回传差异,不放宽校验。外部VOC如已符合固定版本及已记录的.mcp.json路径适配就复用。更新后的新任务应实际发现并加载r10;文件校验、宿主加载分别反馈。
- 仅做只读 smoke:默认研究 contract 核 backend=parse-postgresql、mode=ssh-channel(原Agent通道)及实际当前代际;业务 workshop-contract 读取成功。不要新建会话/项目/报告、采集、发布新代际或重跑验收保存记录。
- ## 2. 发布静态手册
- 建议正式地址为 https://kangshifu.fmode.cn/workshop-manual/ ,以维护人核实的Nginx/静态目录和路由为准。manual/四文件保持同目录,以新版本目录暂存后整体切换;存在旧手册先备份,不能直接覆盖来源不明的文件。独立静态路径不要误落回看板SPA首页;不更换看板根首页、不改数据库/API代理。亦可使用已有授权的静态发布域名,回传真实可访问地址。
- HTML用text/html、CSS用text/css、JS用application/javascript,均UTF-8。入口和版本资源及时回源;若存在CDN,只刷新这四个手册文件。该站点不含密钥、登录或业务数据,不会写数据库。
- ## 3. 一次回传验收
- - Skill:版本r10,43文件逐项一致,7运行文件未变;新任务实际加载状态;研究/业务只读合同结果。
- - 手册:真实网页URL,4文件HTTP200、Content-Type和SHA与清单一致;浏览器打开不是看板回退页。
- - 浏览器:15章导航、完整复制、桌面与390px,无脚本错误;安装提示词指向本执行单固定的r10资源。
- - 范围:准备2节、研究13节、三个半天单一路线;没有49天行动计划或两分支;个人勾选只存本机。
- - 保持:数据库和既有业务记录不改,身份/连接不改。挂站完成与豆包员工电脑安装生效分开反馈;全量小组展示和豆包实跑按后续任务验收。
- '''
- def stage():
- released = json.loads((ROOT / 'docs/workshop-onboarding-20261109-r5/online-release-receipt.json').read_bytes())
- assert released['published'] and released['allPublicFilesReadBack'] and released['workshopSkillVersion'] == 'r10'
- source = {n: (SOURCE / n).read_bytes() for n in NAMES}
- assert b'__R10_INSTALL_GUIDE__' not in source['content.js']
- assert released['guideUrl'].encode() in source['content.js']
- html = source['index.html'].decode('utf-8')
- html = html.replace('<link rel="stylesheet" href="manual.css">', '<style>\n' + source['manual.css'].decode('utf-8') + '\n</style>')
- for name in ['content.js', 'manual.js']:
- script = source[name].decode('utf-8').replace('</script', '<\\/script')
- html = html.replace('<script src="' + name + '" defer></script>', '')
- html = html.replace('</body>', '<script>\n' + script + '\n</script>\n</body>')
- package = {'manual/' + n: b for n, b in source.items()}
- package['ksf-workshop-manual-r10.html'] = html.encode('utf-8')
- package['DEPLOY.md'] = DEPLOY.format(guide=released['guideUrl']).encode('utf-8')
- meta = {'schema': 'ksf.workshop-manual.v1', 'releaseId': RELEASE, 'workshopSkillVersion': 'r10',
- 'installationGuide': released['guideUrl'], 'installationCommit': released['commit'],
- 'sourceFiles': {n: sha(b) for n, b in source.items()},
- 'manualFiles': {n: sha(b) for n, b in package.items()},
- 'halfDays': 3, 'preparationSteps': 2, 'researchSteps': 13,
- 'branches': 1, 'promptsExpanded': True, 'postWorkshopPlanIncluded': False,
- 'databaseIncluded': False, 'credentialsIncluded': False, 'writesBusinessData': False}
- package['release.json'] = encode(meta)
- package['FILES.sha256'] = ''.join(sha(b) + ' ' + n + '\n' for n, b in sorted(package.items())).encode('utf-8')
- for name, b in package.items():
- p = OUT / 'package' / name
- p.parent.mkdir(parents=True, exist_ok=True)
- p.write_bytes(b)
- # Convenient local preview, separate from the immutable public archive.
- (OUT / 'ksf-workshop-manual-r10.html').write_bytes(package['ksf-workshop-manual-r10.html'])
- return package, meta
- def main():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--pack', action='store_true')
- args = parser.parse_args()
- archive = OUT / 'publish' / (RELEASE + '.zip')
- assert not archive.exists(), 'NEVER_OVERWRITE_RELEASE'
- package, meta = stage()
- if not args.pack:
- print(json.dumps({'ok': True, 'staged': len(package), 'preview': str(OUT / 'ksf-workshop-manual-r10.html')}))
- return
- browser = json.loads((SOURCE / 'browser-verification.json').read_bytes())
- assert browser['ok'] and browser['sourceFiles'] == meta['sourceFiles']
- assert browser['standaloneVerified'] and browser['standaloneSha256'] == sha(package['ksf-workshop-manual-r10.html'])
- archive.parent.mkdir(parents=True, exist_ok=True)
- with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED) as z:
- for name, b in sorted(package.items()):
- info = zipfile.ZipInfo(name, (2026, 11, 10, 0, 0, 0))
- info.external_attr = 0o100644 << 16
- z.writestr(info, b, compress_type=zipfile.ZIP_DEFLATED)
- with zipfile.ZipFile(archive) as z:
- assert z.testzip() is None and set(z.namelist()) == set(package)
- assert all(z.read(n) == b for n, b in package.items())
- distribution = {'schema': 'ksf.workshop-manual.distribution.v1', 'releaseId': RELEASE,
- 'archive': {'path': archive.name, 'bytes': archive.stat().st_size, 'sha256': sha(archive.read_bytes())},
- 'workshopSkillVersion': 'r10', 'installationGuide': meta['installationGuide'],
- 'manualFiles': meta['manualFiles'], 'browserLocalVerified': True,
- 'employeeWorkshopTrialCompleted': False, 'productionWebsiteDeployed': False,
- 'suggestedWebsiteUrl': 'https://kangshifu.fmode.cn/workshop-manual/'}
- (archive.parent / 'manual-release.json').write_bytes(encode(distribution))
- (archive.parent / 'DEPLOY.md').write_bytes(package['DEPLOY.md'])
- (OUT / 'build-receipt.json').write_bytes(encode(distribution))
- print(json.dumps(distribution, ensure_ascii=False, indent=2))
- if __name__ == '__main__':
- main()
|