"""Freeze the cumulative full-schema / ingestion patch, without private databases or credentials.""" 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] BASES = ['f4bdaa70a1f9ceac7c44f79a32d7454143875fd5', 'bc5a0092890803d96d790ab782cd9b1069661821'] BASE = BASES[0] NAME = 'ksf-full-schema-intake-20261106-r1' FRONT = ['src/core/library.js', 'src/pages/library-product-detail.js'] BACK = ['server/voc-library/' + n for n in ('board_library.py', 'product_insights.py', 'update_library.py', 'agent_batch.py', 'voc_db.py', 'schema_catalog.py', 'intake_contract.py', 'structured_batch.py')] DEPS = ['server/voc-library/' + n for n in ('retrieval.py', 'schema.sql', 'taxonomy.json')] 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'); p.add_argument('--homepage-file', required=True) args = p.parse_args() commit = subprocess.check_output(['git', 'rev-parse', args.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))) olds = [zipfile.ZipFile(io.BytesIO(subprocess.check_output(['git', 'archive', '--format=zip', b], cwd=ROOT))) for b in BASES] old = olds[0] out = ROOT / 'artifacts/全表字段与Agent入库-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(name, body): dest = package / name; dest.parent.mkdir(parents=True, exist_ok=True); dest.write_bytes(body) def js(name, value): write(name, (json.dumps(value, ensure_ascii=False, indent=2) + '\n').encode()) for name in FRONT + BACK: write('payload/' + name, frozen.read(name)) for n in ('ksfGetDataDictionary.js','manifest.json'): write('functions/' + n, (ROOT / 'server/dist/data-dictionary' / n).read_bytes()) for n in ('postgres.mjs','core.mjs','data-dictionary.mjs'): assert (ROOT/'server/src'/n).read_bytes().replace(b'\r\n',b'\n') == frozen.read('server/src/'+n).replace(b'\r\n',b'\n'), 'FUNCTION_SOURCE_NOT_FROZEN' assert (ROOT/'server/references/business-data-dictionary.json').read_bytes().replace(b'\r\n',b'\n') == frozen.read('server/references/business-data-dictionary.json').replace(b'\r\n',b'\n') function_manifest = json.loads((package / 'functions/manifest.json').read_text(encoding='utf-8')) for item in function_manifest['functions']: assert sha((package / 'functions' / item['file']).read_bytes()) == item['sha256'] write('发给王刚001.md', frozen.read('docs/full-schema-intake-20261106/发给王刚001.md')) for name in frozen.namelist(): if name.startswith('skills/') and not name.endswith('/'): write(name, frozen.read(name)) write('install-query-skills.py', frozen.read('scripts/install-query-skills.py')) baselines = {name: [{p.split('/', 2)[2]: sha(old.read(p)) for p in old.namelist() if p.startswith('skills/' + name + '/') and not p.endswith('/')} for old in olds] for name in ('ksf-workshop-runtime', 'ksf-voc-library')} js('baselines.json', baselines) def entry(name): prior = [o.read(name) for o in olds if name in o.namelist()] return dict(path=name, sha256=sha(frozen.read(name)), priorNormalizedSha256=sorted(set(norm(b) for b in prior)), allowMissing=len(prior)