| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- """Freeze the latest approved library and industry UI with read-only industry runtime."""
- import argparse
- import ast
- from concurrent.futures import ThreadPoolExecutor
- from datetime import datetime, timezone
- import difflib
- import hashlib
- import json
- from pathlib import Path
- import re
- import shutil
- from urllib.error import HTTPError
- from urllib.request import Request, urlopen
- import zipfile
- ROOT = Path(__file__).resolve().parents[1]
- RELEASE = 'ksf-board-research-20261108-r13'
- OUT = ROOT / 'artifacts' / RELEASE
- PACKAGE = OUT / 'package'
- WORK = ROOT / 'work' / RELEASE
- PRIOR = ROOT / 'artifacts/ksf-industry-monitor-20261107-r11/package'
- def sha(raw):
- return hashlib.sha256(raw).hexdigest()
- def save(path, data):
- path.parent.mkdir(parents=True, exist_ok=True)
- path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
- def dependencies():
- selected, required = set(), set()
- queue = ['src/layout/kf-app.js', 'src/layout/kf-nav.js', 'src/utils/router.js',
- 'src/pages/view-library.js', 'src/pages/view-industry.js']
- while queue:
- name = queue.pop()
- if name in selected:
- continue
- selected.add(name)
- for rel in re.findall(r'(?:from\s*|import\s*)[\'\"]([.][^\'\"]+)[\'\"]', (ROOT / name).read_text(encoding='utf-8')):
- target = (ROOT / name).parent.joinpath(rel).resolve().relative_to(ROOT).as_posix()
- if any(part in target for part in ['/industry-', '/policy-reviewed-', '/library-', '/library.']):
- queue.append(target)
- else:
- required.add(target)
- return selected, required - selected
- def capture(name):
- try:
- with urlopen(Request('https://kangshifu.fmode.cn/' + name, headers={'Cache-Control': 'no-cache'}), timeout=35) as response:
- raw = response.read(3_000_001)
- content_type = response.headers.get('Content-Type', '')
- assert len(raw) < 3_000_001, name
- exists = name == 'index.html' or 'javascript' in content_type or 'text/css' in content_type
- if not exists:
- assert b'<html' in raw[:500].lower() or b'<!doctype' in raw[:500].lower(), name
- if exists:
- target = WORK / 'public-baseline' / name
- target.parent.mkdir(parents=True, exist_ok=True)
- target.write_bytes(raw)
- return dict(path=name, exists=exists, sha256=sha(raw) if exists else None,
- normalizedSha256=sha(raw.replace(b'\r\n', b'\n')) if exists else None)
- except HTTPError as error:
- assert error.code == 404, (name, error.code)
- return dict(path=name, exists=False, sha256=None, normalizedSha256=None)
- def build():
- assert not PACKAGE.exists(), 'NEW_RELEASE_REQUIRED'
- static, required = dependencies()
- with ThreadPoolExecutor(max_workers=8) as pool:
- baseline = list(pool.map(capture, sorted(static | required | {'index.html'})))
- observed = {row['path']: row for row in baseline}
- assert all(observed[name]['exists'] for name in required), 'MISSING_PUBLIC_DEPENDENCY'
- assert observed['index.html']['sha256'] == 'dfeb6a6d0b63e8e1190f9308bf6619a5ca523a5a2f91af5f7ca5ad5fc9642db3'
- captured = {}
- def copy(name, dest=None, normalize=False):
- raw = (ROOT / name).read_bytes()
- captured[name] = sha(raw)
- output = PACKAGE / (dest or name)
- output.parent.mkdir(parents=True, exist_ok=True)
- output.write_bytes(raw.replace(b'\r\n', b'\n') if normalize else raw)
- files, patch = [], []
- for name in sorted(static):
- copy(name, 'payload/' + name, True)
- raw = (PACKAGE / 'payload' / name).read_bytes()
- old = observed[name]
- files.append(dict(path=name, sha256=sha(raw), allowMissing=not old['exists'],
- priorNormalizedSha256=[old['normalizedSha256']] if old['exists'] else [],
- observedProductionSha256=old['sha256']))
- before = (WORK / 'public-baseline' / name).read_text(encoding='utf-8') if old['exists'] else ''
- patch.extend(difflib.unified_diff(before.splitlines(True), raw.decode().splitlines(True), fromfile='public/' + name, tofile='r13/' + name))
- backend = json.loads((PRIOR / 'RELEASE.json').read_bytes())['backendFiles'] + [
- 'server/voc-library/industry_workshop.py', 'server/voc-library/industry_decisions.py', 'server/voc-library/policy_usage_review.py']
- for name in backend:
- copy(name)
- for path in (ROOT / 'server/voc-library').glob('test_*.py'):
- if path.name.startswith('test_industry_') or path.name == 'test_policy_usage_review.py':
- copy(path.relative_to(ROOT).as_posix())
- node_tests = [p for p in (ROOT / 'test').glob('*.test.js') if (p.name.startswith(('industry-', 'library')) or p.name == 'router.test.js')
- and p.name not in ('library-comparison.test.js', 'industry-collection-guard.test.js')]
- for path in node_tests:
- name = path.relative_to(ROOT).as_posix()
- copy(name)
- dest = PACKAGE / name
- dest.write_text(dest.read_text(encoding='utf-8').replace('../src/', '../payload/src/'), encoding='utf-8')
- # Library API tests import this existing unchanged transport; it is test support only.
- copy('server/library-api.mjs', 'test-support/library-api.mjs')
- p = PACKAGE / 'test/library.test.js'
- p.write_text(p.read_text(encoding='utf-8').replace('../server/library-api.mjs', '../test-support/library-api.mjs'), encoding='utf-8')
- public_config = (WORK / 'public-baseline/src/app.config.js').read_bytes()
- (PACKAGE / 'payload/src/app.config.js').write_bytes(public_config)
- copy('scripts/verify-industry-existing-data.py')
- copy('scripts/query-industry-policy-usage.py')
- copy('scripts/package-board-research-release.py')
- for name in ['19-已核政策原文与用法查询.md', '20-上方范围与产品人群筛选扩展.md', 'agent-queries.md']:
- copy('docs/industry-monitoring-20261107/' + name, 'docs/' + name)
- policy = ROOT / 'artifacts/ksf-industry-policy-usage-20261108-r1/package'
- shutil.copytree(policy / 'sources', PACKAGE / 'data/policy-review/sources')
- shutil.copyfile(policy / 'policy-usage.json', PACKAGE / 'data/policy-review/policy-usage.json')
- assert sha((PACKAGE / 'data/policy-review/policy-usage.json').read_bytes()) == 'd22574f1ce28e1487e0be2abb78c47b32d22de801dc6e54ae9a8e3b3d31e777f'
- publisher = (PRIOR / 'scripts/publish-industry-ui.mjs').read_text(encoding='utf-8')
- old = next(line for line in publisher.splitlines() if line.startswith('export const FILES='))
- publisher = publisher.replace(old, 'export const FILES=Object.freeze(' + json.dumps(sorted(static)) + ');').replace('ksf-industry-monitor-20261107-r11', RELEASE)
- (PACKAGE / 'scripts/publish-industry-ui.mjs').write_text(publisher, encoding='utf-8')
- save(PACKAGE / 'deploy/industry-ui-files.json', dict(version='ksf.industry-ui.publish.v1', release=RELEASE, homepageSha256=observed['index.html']['sha256'], files=files))
- save(PACKAGE / 'evidence/production-baseline.json', dict(observedAt=datetime.now(timezone.utc).isoformat(), files=baseline))
- (PACKAGE / 'evidence/ui.patch').write_text(''.join(patch), encoding='utf-8')
- save(PACKAGE / 'package.json', dict(type='module', private=True))
- save(PACKAGE / 'RELEASE.json', dict(release=RELEASE, createdAt=datetime.now(timezone.utc).isoformat(),
- scope='Latest approved library and innovation analysis UI, read-only industry service and reviewed policies',
- staticFiles=sorted(static), backendFiles=backend, sourceFilesSha256=captured,
- generation='82b104fc024b49a75d2f5376bf9778ced6f63a9433b5fc5a6daa3c458d05d5ea',
- policyReviewSha256='d22574f1ce28e1487e0be2abb78c47b32d22de801dc6e54ae9a8e3b3d31e777f',
- databaseIncluded=False, existingVocCoreModulesReplaced=0, credentialsIncluded=False,
- productionDeployed=False, nativeProductionRuntimeVerified=False, paidCalls=0))
- for name, digest in captured.items():
- assert sha((ROOT / name).read_bytes()) == digest, 'SOURCE_CHANGED_DURING_FREEZE:' + name
- for path in (PACKAGE / 'server').rglob('*.py'):
- ast.parse(path.read_text(encoding='utf-8-sig'), feature_version=(3, 9))
- print(json.dumps(dict(ok=True, package=str(PACKAGE), staticFiles=len(static), backendFiles=len(backend), sealed=False)))
- def seal():
- receipt = json.loads((PACKAGE / 'evidence/acceptance.json').read_bytes())
- assert receipt['ok'] and receipt['publisher']['ok'] and receipt['browser']['ok']
- for name, digest in receipt['testedFilesSha256'].items():
- assert sha((PACKAGE / name).read_bytes()) == digest, 'TESTED_FILE_CHANGED:' + name
- assert not list(PACKAGE.rglob('*.sqlite')) and not list(PACKAGE.rglob('*.db'))
- assert not list(PACKAGE.rglob('snapshot.json'))
- files = sorted(p for p in PACKAGE.rglob('*') if p.is_file() and p.name != 'FILES.sha256')
- (PACKAGE / 'FILES.sha256').write_text(''.join(sha(p.read_bytes()) + ' ' + p.relative_to(PACKAGE).as_posix() + '\n' for p in files), encoding='utf-8')
- archive = OUT / (RELEASE + '.zip')
- assert not archive.exists(), 'SEALED_ARCHIVE_ALREADY_EXISTS'
- with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED) as bundle:
- for path in sorted(PACKAGE.rglob('*')):
- if path.is_file():
- bundle.write(path, path.relative_to(PACKAGE).as_posix())
- with zipfile.ZipFile(archive) as bundle:
- assert bundle.testzip() is None
- for line in bundle.read('FILES.sha256').decode().splitlines():
- digest, name = line.split(' ', 1)
- assert sha(bundle.read(name)) == digest
- save(OUT / 'delivery.json', dict(ok=True, archive=str(archive), bytes=archive.stat().st_size, sha256=sha(archive.read_bytes()), manifestEntries=len(files), productionDeployed=False, paidCalls=0))
- print((OUT / 'delivery.json').read_text(encoding='utf-8'))
- if __name__ == '__main__':
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--seal', action='store_true')
- (seal if parser.parse_args().seal else build)()
|