package-unified-workshop-skill.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. """Build a strictly enumerated public Skill upgrade; never reads installed credentials."""
  2. from pathlib import Path
  3. import datetime
  4. import hashlib
  5. import json
  6. import zipfile
  7. ROOT = Path(__file__).resolve().parents[1]
  8. OUT = ROOT / 'docs/workshop-skill-release-20261108-r2'
  9. NAME = 'ksf-workshop-unified-20261108-r2'
  10. sha = lambda body: hashlib.sha256(body).hexdigest()
  11. def main():
  12. skill = ROOT / 'skills/ksf-workshop-runtime'
  13. candidate = {}
  14. files = {}
  15. for line in (skill / 'FILES.sha256').read_text(encoding='utf-8').splitlines():
  16. digest, name = line.split(' ', 1)
  17. assert name not in candidate and '..' not in Path(name).parts and not Path(name).is_absolute()
  18. raw = (skill / name).read_bytes()
  19. assert sha(raw) == digest
  20. candidate[name] = digest
  21. files['skill/' + name] = raw
  22. assert len(candidate) == 37
  23. raw = (skill / 'FILES.sha256').read_bytes()
  24. candidate['FILES.sha256'] = sha(raw)
  25. files['skill/FILES.sha256'] = raw
  26. received = ROOT / 'docs/workshop-skill-review-20261108/received-15ffc90f'
  27. export = json.loads((received / 'EXPORT.json').read_text(encoding='utf-8-sig'))
  28. baseline = {}
  29. for entry in export['files']:
  30. raw = (received / 'ksf-workshop-runtime' / entry['path']).read_bytes()
  31. assert sha(raw) == entry['sha256']
  32. baseline[entry['path']] = entry['sha256']
  33. assert len(baseline) == 14
  34. prior_path = ROOT / 'docs/agent-library-read-repair-20261108/receipts/r2/agent-library-read-receipt.json'
  35. prior = json.loads(prior_path.read_text(encoding='utf-8-sig'))
  36. assert prior['ok'] and prior['installedFilesUnchanged']
  37. assert prior['skillFingerprint'] == 'b4266efed78c19c0162aa2200cf0be83b0e7afa2473cef134459eb14c97ce69c'
  38. incoming = OUT / 'received-library-2773fc95'
  39. current_raw = (incoming / 'ksf-voc-library-SKILL.md').read_bytes()
  40. current_sha = sha(current_raw)
  41. assert current_sha == '2773fc95fd98fa2367390c72ac5de20435c4a6458cfea45e2b847657af56630a'
  42. incoming_zip_sha = sha((incoming / 'ksf-voc-library-current-20261108-v1.zip').read_bytes())
  43. assert incoming_zip_sha == '6a9ab3e28cbc7ee53194d2be5c43a04e1bbc929a44c3d35eeb9055f9adf01aa3'
  44. # Names are the known installed layout. Only four runtime hashes have an
  45. # independent accepted read receipt; other members still require on-site manifest checks.
  46. declared = dict((name, digest) for digest, name in
  47. (line.split(' ', 1) for line in (ROOT / 'skills/ksf-voc-library/FILES.sha256').read_text(encoding='utf-8').splitlines()))
  48. assert len(declared) == 19 and declared['SKILL.md'] == prior['skillFingerprint']
  49. runtime_pins = {'scripts/' + name: digest for name, digest in prior['installedFingerprints'].items()}
  50. assert len(runtime_pins) == 4 and all(declared[n] == h for n, h in runtime_pins.items())
  51. expected = {'schema': 'ksf.agent-library-read-expected.v1',
  52. 'generation': prior['contract']['generation'],
  53. 'sourceReceiptSha256': sha(prior_path.read_bytes()),
  54. 'skillFingerprint': candidate['SKILL.md'],
  55. 'installedFingerprints': prior['installedFingerprints']}
  56. release = {'schema': 'ksf.unified-workshop-release.v1', 'releaseId': NAME,
  57. 'createdAt': datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))).isoformat(),
  58. 'baseline': baseline, 'candidate': candidate,
  59. 'legacySkillSha256': current_sha,
  60. 'legacyManifestPolicy': {
  61. 'entryNames': sorted(declared), 'verifiedRuntimeEntries': runtime_pins,
  62. 'knownStaleSkill': {'actualSha256': current_sha,
  63. 'declaredSha256': prior['skillFingerprint'],
  64. 'evidenceSha256': incoming_zip_sha}},
  65. 'deliveryStage': 'dry-only-await-user-instruction',
  66. 'runtimeReplacementAllowed': False, 'paidCallsAllowed': False,
  67. 'businessWritesDuringUpgrade': False}
  68. files.update({
  69. 'release.json': (json.dumps(release, ensure_ascii=False, indent=2) + '\n').encode(),
  70. 'expected-agent-read.json': (json.dumps(expected, ensure_ascii=False, indent=2) + '\n').encode(),
  71. 'upgrade-skill.mjs': (ROOT / 'scripts/upgrade-unified-workshop-skill.mjs').read_bytes(),
  72. 'probe-agent-library-read.py': (ROOT / 'scripts/probe-agent-library-read.py').read_bytes(),
  73. 'README.md': (OUT / '王刚001-仅dry预检.md').read_bytes(),
  74. 'merge-notes.md': (OUT / '经验合并与预检修正.md').read_bytes(),
  75. 'local-verification.json': (OUT / 'local-verification.json').read_bytes(),
  76. 'prior-r1-local-rehearsal-summary.json': (ROOT / 'docs/workshop-skill-release-20261108-r1/local-rehearsal-summary.json').read_bytes(),
  77. })
  78. for name in ('scope-request.json', 'keyword-request.json'):
  79. files[name] = (prior_path.parent / name).read_bytes()
  80. for name in files:
  81. assert not any(term in name.lower() for term in ('credential-binding', 'private.pem', 'envelope', 'channel-config'))
  82. files['FILES.sha256'] = ''.join(sha(raw) + ' ' + name + '\n' for name, raw in sorted(files.items())).encode()
  83. dest = OUT / (NAME + '.zip')
  84. assert not dest.exists(), 'Use a new revision; do not overwrite a released archive'
  85. with zipfile.ZipFile(dest, 'x', zipfile.ZIP_DEFLATED) as archive:
  86. for name, raw in sorted(files.items()):
  87. info = zipfile.ZipInfo(name, (2026, 11, 8, 0, 0, 0))
  88. info.external_attr = 0o100600 << 16
  89. archive.writestr(info, raw, compress_type=zipfile.ZIP_DEFLATED)
  90. with zipfile.ZipFile(dest) as archive:
  91. assert archive.testzip() is None
  92. for line in archive.read('FILES.sha256').decode().splitlines():
  93. digest, name = line.split(' ', 1)
  94. assert sha(archive.read(name)) == digest
  95. receipt = {'schema': 'ksf.unified-workshop-package-receipt.v1', 'ok': True,
  96. 'filename': dest.name, 'bytes': dest.stat().st_size, 'sha256': sha(dest.read_bytes()),
  97. 'entries': len(files), 'outerManifestMatched': len(files) - 1,
  98. 'skillManifestMatched': len(candidate) - 1, 'baselineFilesPinned': len(baseline),
  99. 'runtimeCodeReplaced': False, 'credentialsIncluded': False, 'deployed': False,
  100. 'agentLoaded': False, 'deliveryStage': release['deliveryStage'],
  101. 'legacyActualSkillSha256': current_sha,
  102. 'legacyKnownStaleDeclarationSha256': prior['skillFingerprint'],
  103. 'businessDatabaseWrites': 0, 'paidCalls': 0}
  104. (OUT / 'package-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  105. print(json.dumps(receipt, ensure_ascii=False))
  106. if __name__ == '__main__':
  107. main()