| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- """Build an enterprise bootstrap package from the unchanged sealed r13 and runtime hotfix."""
- import argparse
- import ast
- import hashlib
- import importlib.util
- import io
- import json
- from pathlib import Path
- import sys
- import zipfile
- sys.dont_write_bytecode = True
- ROOT = Path(__file__).resolve().parents[1]
- SOURCE = ROOT / 'deploy/enterprise-workshop'
- NAME = 'ksf-enterprise-workshop-20261110-r2'
- sha = lambda raw: hashlib.sha256(raw).hexdigest()
- def need(ok, code):
- if not ok:
- raise ValueError(code)
- def content():
- files = {p.name: p.read_bytes() for p in SOURCE.iterdir() if p.is_file()}
- files['.gitattributes'] = b'* -text\n'
- cfg = json.loads(files['enterprise.json'])
- skill_archive = ROOT / 'docs/ksf-workshop-skill-20261110-r13-r2/publish/ksf-workshop-skill-20261110-r13-r2.zip'
- hotfix_archive = ROOT / 'docs/ksf-unified-runtime-hotfix-20261109-r1/publish/ksf-unified-runtime-hotfix-20261109-r1.zip'
- need(sha(skill_archive.read_bytes()) == cfg['skill']['upstreamArchiveSha256'], 'SEALED_SKILL_CHANGED')
- need(sha(hotfix_archive.read_bytes()) == cfg['researchRuntime']['upstreamArchiveSha256'], 'SEALED_HOTFIX_CHANGED')
- with zipfile.ZipFile(skill_archive) as z:
- meta = json.loads(z.read('release.json'))
- for name, digest in meta['workshop']['files'].items():
- raw = z.read('skills/ksf-workshop-runtime/' + name)
- need(sha(raw) == digest, 'SKILL_HASH_MISMATCH')
- files['payload/skills/ksf-workshop-runtime/' + name] = raw
- with zipfile.ZipFile(hotfix_archive) as z:
- prefix = 'components/research-runtime/'
- for line in z.read(prefix + 'FILES.sha256').decode().splitlines():
- digest, name = line.split(' ', 1)
- need(name.startswith('scripts/'), 'UNEXPECTED_RESEARCH_COMPONENT')
- raw = z.read(prefix + name)
- need(sha(raw) == digest, 'RESEARCH_COMPONENT_HASH_MISMATCH')
- files['payload/skills/ksf-voc-library/' + name] = raw
- for name, raw in files.items():
- if name.endswith('.py'):
- ast.parse(raw.decode('utf-8-sig'), filename=name, feature_version=(3, 9))
- files['FILES.sha256'] = ''.join(sha(b) + ' ' + n + '\n' for n, b in sorted(files.items())).encode()
- return files
- def build(output):
- need(not output.exists(), 'NEW_OUTPUT_REQUIRED')
- files = content()
- for name, raw in files.items():
- file = output / name
- file.parent.mkdir(parents=True, exist_ok=True)
- file.write_bytes(raw)
- return files
- def main():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--out', type=Path, required=True)
- args = parser.parse_args()
- files = build(args.out / 'package')
- stream = io.BytesIO()
- with zipfile.ZipFile(stream, 'w', zipfile.ZIP_DEFLATED) as z:
- for name, raw in sorted(files.items()):
- entry = zipfile.ZipInfo(name, (2026, 11, 10, 0, 0, 0))
- entry.external_attr = 0o100644 << 16
- z.writestr(entry, raw, compress_type=zipfile.ZIP_DEFLATED)
- archive = stream.getvalue()
- (args.out / (NAME + '.zip')).write_bytes(archive)
- receipt = {'release': NAME, 'archive': NAME + '.zip', 'bytes': len(archive), 'sha256': sha(archive),
- 'files': len(files), 'skillVersion': 'r13', 'existingSkillFilesChanged': 0,
- 'credentialsIncluded': False, 'productionDeployed': False,
- 'vocVersion': '0.3.44', 'vocRequired': True, 'thirdPartyVocArchiveIncluded': False,
- 'automaticLifecycleHookInstalled': False, 'businessWrites': 0}
- (args.out / 'release.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
- print(json.dumps(receipt, ensure_ascii=False, indent=2))
- if __name__ == '__main__':
- main()
|