"""Freeze the Python3.9 / cross-host library channel patch from a committed revision.""" import argparse import hashlib import importlib.util import io import json from pathlib import Path import shutil import subprocess import tempfile import zipfile ROOT=Path(__file__).resolve().parents[1] BASE='cd40f22a047cbaacfe458d550670d1fbff6762d3' NAME='ksf-library-publish-20261106-r1' MODULES=('voc_db.py','agent_batch.py','update_library.py','library_channel.py','library_receiver.py') DEPS=('schema.sql','taxonomy.json','retrieval.py','schema_catalog.py','intake_contract.py','structured_batch.py','product_insights.py','board_library.py') sha=lambda b:hashlib.sha256(b).hexdigest() norm=lambda b:sha(b.replace(b'\r\n',b'\n')) def main(): p=argparse.ArgumentParser(description=__doc__);p.add_argument('--commit',default='HEAD');a=p.parse_args() commit=subprocess.check_output(['git','rev-parse',a.commit+'^{commit}'],cwd=ROOT).decode().strip() subprocess.run(['git','merge-base','--is-ancestor',BASE,commit],cwd=ROOT,check=True) frozen=zipfile.ZipFile(io.BytesIO(subprocess.check_output(['git','archive','--format=zip',commit],cwd=ROOT))) old=zipfile.ZipFile(io.BytesIO(subprocess.check_output(['git','archive','--format=zip',BASE],cwd=ROOT))) out=ROOT/'artifacts/跨主机入库与兼容修复-20261106';package=ROOT/'work'/NAME/'package' if package.exists() or (out/(NAME+'.zip')).exists():raise ValueError('RELEASE_ALREADY_EXISTS') package.mkdir(parents=True) def write(n,b): q=package/n;q.parent.mkdir(parents=True,exist_ok=True);q.write_bytes(b) def js(n,v):write(n,(json.dumps(v,ensure_ascii=False,indent=2)+'\n').encode()) def entry(n):return dict(path=n,sha256=sha(frozen.read(n)),priorNormalizedSha256=[norm(old.read(n))] if n in old.namelist() else [],allowMissing=n not in old.namelist()) for n in MODULES:write('payload/server/voc-library/'+n,frozen.read('server/voc-library/'+n)) for n in frozen.namelist(): if n.startswith('skills/') and not n.endswith('/'):write(n,frozen.read(n)) for n in ('ksfGetDataDictionary.js','manifest.json'):write('functions/'+n,(ROOT/'server/dist/data-dictionary'/n).read_bytes()) for n in ('server/src/postgres.mjs','server/src/core.mjs','server/src/data-dictionary.mjs','server/references/runtime-data.json','server/references/business-data-dictionary.json'): assert norm((ROOT/n).read_bytes())==norm(frozen.read(n)),'FUNCTION_SOURCE_NOT_FROZEN' function=json.loads((package/'functions/manifest.json').read_text(encoding='utf-8')) assert function['functions'][0]['name']=='ksfGetDataDictionary' and len(function['functions'])==1 for item in function['functions']:assert sha((package/'functions'/item['file']).read_bytes())==item['sha256'] function['functions'][0].update(objectId='Z41ABwyxHO',beforeSha256='e53f148c4ca34085c176f595c2f402c107ce95bcde6107840932cc400d2fe08e',operation='update-code-only') function['existingFunctionsModified']=True;function['scope']='only ksfGetDataDictionary code; preserve ACL/schema/other functions' js('functions/manifest.json',function) js('deploy/backend-files.json',{'files':[entry('server/voc-library/'+n) for n in MODULES]}) js('deploy/dependencies.json',{'files':[dict(path='server/voc-library/'+n,normalizedSha256=norm(frozen.read('server/voc-library/'+n))) for n in DEPS]}) for n in ('发给运维001.md','发给王刚001.md','实现与验证.md'):write(n,frozen.read('docs/library-publish-20261106/'+n)) write('install-query-skills.py',frozen.read('scripts/install-query-skills.py')) for n in ('install-voc-database.py','verify-installed-voc-database.py','reassemble-voc-database.py'): write('utilities/'+n,frozen.read('scripts/'+n)) baselines={name:[{n.split('/',2)[2]:sha(old.read(n)) for n in old.namelist() if n.startswith('skills/'+name+'/') and not n.endswith('/')}] for name in ('ksf-workshop-runtime','ksf-voc-library')} js('baselines.json',baselines) js('examples/channel.json',{'schema':'ksf.library-channel.v1','host':'172.0.0.100','port':22,'user':'ksf-library-publisher', 'identityFile':'/opt/data/ksf-library-channel/id_ed25519','knownHostsFile':'/opt/data/ksf-library-channel/known_hosts'}) js('examples/receiver.json',{'schema':'ksf.library-channel.v1','registry':'/opt/ksf-voc-library/registry','intake':'/opt/ksf-voc-intake','canPublish':True}) spec=importlib.util.spec_from_file_location('channel_skill_installer',package/'install-query-skills.py');installer=importlib.util.module_from_spec(spec);spec.loader.exec_module(installer) skills={} for name in baselines: folder=package/'skills'/name for line in (folder/'FILES.sha256').read_text(encoding='utf-8').splitlines(): h,n=line.split(' ',1);assert sha((folder/n).read_bytes())==h skills[name]={'entries':len((folder/'FILES.sha256').read_text().splitlines()),'manifestSha256':sha((folder/'FILES.sha256').read_bytes())} with tempfile.TemporaryDirectory(prefix='install-check-',dir=package.parent) as td: data=Path(td) for name,versions in baselines.items(): for n in versions[0]: target=data/'skills'/name/n;target.parent.mkdir(parents=True,exist_ok=True);target.write_bytes(old.read('skills/'+name+'/'+n)) binding=data/'skills/ksf-workshop-runtime/credential-binding.json';binding.write_text('preserved') session=data/'ksf-workshop-state/sessions/existing.json';session.parent.mkdir(parents=True);session.write_text('preserved') plan=installer.install(package,data);assert plan['filesChecked']==34 assert installer.install(package,data,True)['filesystemVerified'];assert not installer.install(package,data,True)['applied'] assert binding.read_text()==session.read_text()=='preserved' js('RELEASE.json',dict(release=NAME,sourceCommit=commit,baselineCommit=BASE,skills=skills,installerCheck=plan, installerApplyVerified=True,installerNoOpVerified=True,backendFiles=len(MODULES),staticFiles=0, cloudFunctions=function['functions'],businessDeclaredFields=1388,businessPhysicalSnapshotFields=1451, pythonMinimum='3.9',databaseIncluded=False,credentialsIncluded=False,productionInstalled=False, sshChannelConfigured=False,productionIngestionVerified=False)) files=sorted(q for q in package.rglob('*') if q.is_file()) assert not any(q.suffix in ('.sqlite','.db','.key','.pyc') or q.name.startswith('.env') for q in files) write('FILES.sha256',''.join(sha(q.read_bytes())+' '+q.relative_to(package).as_posix()+'\n' for q in files).encode()) out.mkdir(parents=True,exist_ok=True);archive=out/(NAME+'.zip') with zipfile.ZipFile(archive,'x',zipfile.ZIP_DEFLATED,compresslevel=9) as z: for q in [*files,package/'FILES.sha256']:z.write(q,q.relative_to(package).as_posix()) with zipfile.ZipFile(archive) as z: assert z.testzip() is None for line in z.read('FILES.sha256').decode().splitlines(): h,n=line.split(' ',1);assert sha(z.read(n))==h receipt=dict(ok=True,filename=archive.name,bytes=archive.stat().st_size,sha256=sha(archive.read_bytes()),entries=len(files)+1, filesVerified=len(files),sourceCommit=commit,productionInstalled=False) (out/'delivery-receipt.json').write_text(json.dumps(receipt,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') for n in ('发给运维001.md','发给王刚001.md','实现与验证.md'):shutil.copyfile(package/n,out/n) print(json.dumps(receipt,ensure_ascii=False,indent=2)) if __name__=='__main__':main()