| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- """Freeze r11 on sealed r10 with publication identity checks and a concrete ops handoff."""
- import argparse
- from contextlib import closing
- from datetime import datetime, timezone
- import hashlib
- import json
- from pathlib import Path
- import shutil
- import sqlite3
- import zipfile
- ROOT=Path(__file__).resolve().parents[1]
- BASE=ROOT/'artifacts/ksf-industry-monitor-20261107-r10/package'
- WORK=ROOT/'work/industry-mainline-finish-20261107-r1'
- RELEASE='ksf-industry-monitor-20261107-r11'
- SOURCES=['server/voc-library/policy_publications.py','server/voc-library/test_policy_publications.py',
- 'server/voc-library/monitoring_policy.py','server/voc-library/industry_policy.py',
- 'src/pages/industry-policy-view.js','scripts/register-industry-policy.py',
- 'scripts/verify-industry-monitor.py','scripts/verify-industry-publications-release.py',
- 'scripts/package-industry-publications.py']
- DOCS=['deployment.md','agent-queries.md','主线待决策清单.md','发给VOC采集方-只读核查与报价.md',
- '交给运维001-主线接通执行单.md','r11正式环境核通与政策发布核验.md']
- def sha(raw):return hashlib.sha256(raw).hexdigest()
- def save(path,value):
- path.parent.mkdir(parents=True,exist_ok=True)
- path.write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n','utf-8')
- def build(out):
- if out.exists():raise ValueError('RELEASE_DIRECTORY_MUST_BE_NEW')
- for line in (BASE/'FILES.sha256').read_text('utf-8').splitlines():
- digest,name=line.split(' ',1)
- if sha((BASE/name).read_bytes())!=digest:raise ValueError('FROZEN_R10_CHANGED')
- captured={name:(ROOT/name).read_bytes() for name in SOURCES}
- shutil.copytree(BASE,out)
- for name,raw in captured.items():
- target=out/('payload/'+name if name.startswith('src/') else name)
- target.parent.mkdir(parents=True,exist_ok=True);target.write_bytes(raw)
- if any((ROOT/name).read_bytes()!=raw for name,raw in captured.items()):raise ValueError('SOURCE_CHANGED_DURING_FREEZE')
- save(out/'evidence/r11-source-capture.json',{'release':RELEASE,'sourceFilesSha256':{n:sha(v) for n,v in captured.items()},
- 'viewIndustrySource':'sealed r10; concurrent workspace innovation edits excluded'})
- store=ROOT/'work/industry-monitoring-20261107-r1/monitoring-registry'
- with closing(sqlite3.connect((store/'monitoring.sqlite').resolve().as_uri()+'?mode=ro',uri=True)) as source:
- with closing(sqlite3.connect(out/'data/monitoring-registry/monitoring.sqlite')) as target:
- source.backup(target);target.execute("UPDATE pipeline_workers SET state='ARCHIVED_COPY'")
- target.execute("UPDATE dispatch_audits SET state='ARCHIVED_COPY'");target.commit()
- for file in (store/'assets').iterdir():
- if sha(file.read_bytes())!=file.stem:raise ValueError('AUXILIARY_ASSET_CHANGED')
- target=out/'data/monitoring-registry/assets'/file.name
- if not target.exists():shutil.copyfile(file,target)
- for name in ['current-production-probe.json','configured-existing-data-counts.json','official-probe.json']:
- shutil.copyfile(WORK/name,out/'evidence'/('r11-'+name))
- for name in DOCS:shutil.copyfile(ROOT/'docs/industry-monitoring-20261107'/name,out/'docs'/name)
- release=json.loads((out/'RELEASE.json').read_bytes())
- release.update(release=RELEASE,createdAt=datetime.now(timezone.utc).isoformat(),
- builtFromFrozenRelease='ksf-industry-monitor-20261107-r10',policyPublicationIdentityVerified=4,
- archivedPublicationObservedDate='2026-11-04',nativePython39Verified=False,
- productionApplicationConnectionVerified=True,productionIndustryEndpointStatus=404,
- productionDeploymentExecutor='运维001',overallComplete=False,productionDeployed=False)
- release['backendFiles'].append('server/voc-library/policy_publications.py');save(out/'RELEASE.json',release)
- path=out/'deploy/industry-ui-files.json';manifest=json.loads(path.read_bytes());manifest['release']=RELEASE
- for entry in manifest['files']:
- previous=sha((BASE/'payload'/entry['path']).read_bytes().replace(b'\r\n',b'\n'))
- if previous not in entry['priorNormalizedSha256']:entry['priorNormalizedSha256'].append(previous)
- entry['sha256']=sha((out/'payload'/entry['path']).read_bytes().replace(b'\r\n',b'\n'))
- save(path,manifest)
- publisher=out/'scripts/publish-industry-ui.mjs'
- publisher.write_text(publisher.read_text('utf-8').replace('ksf-industry-monitor-20261107-r10',RELEASE),'utf-8')
- for file in (out/'deploy').iterdir():
- if file.suffix=='.example':file.write_text(file.read_text('utf-8').replace('ksf-industry-monitor-20261107-r10',RELEASE),'utf-8')
- (out/'docs/README.md').write_text('''# 方便面行业VOC r11 本地验收与运维接通版
- 以封存r10为基础,新增公告/膳食指导/问答发布原文核验,14个前端模块、17个独立后端文件。4份历史发布原文已按研究库SHA核实,9份标准状态另列;不把历史发布证明写成当前有效性。
- 正式应用API已独立核通,研究接口正常;行业接口当前404。用户指定部署执行者为运维001。请先读《交给运维001-主线接通执行单.md》并按deployment.md受控执行,旧版本文档/回执仅作来源,不重复发布。当前验收见implementation-receipt-v11.json。
- 本包不含应用配置、密钥、私有详情原件或活动研究库。主线全量数据、榜单、持续监听及生产上线仍分别待验,overallComplete=false。收费保持停止;原¥46.20已扣、¥3.80保底保留。封存r5至r10不变。
- ''','utf-8')
- print(json.dumps({'ok':True,'candidate':str(out),'staticFiles':14,'backendFiles':17,'sealed':False}))
- def seal(out):
- receipt=json.loads((out/'evidence/implementation-receipt-v11.json').read_bytes())
- if not receipt['accepted']:raise ValueError('ACCEPTANCE_REQUIRED')
- for name,digest in receipt['testedFilesSha256'].items():
- if sha((out/name).read_bytes())!=digest:raise ValueError('TESTED_FILE_CHANGED')
- archive=out.parent/(RELEASE+'.zip')
- if archive.exists():raise ValueError('ARCHIVE_EXISTS')
- files=sorted(f for f in out.rglob('*') if f.is_file() and f!=out/'FILES.sha256')
- (out/'FILES.sha256').write_text(''.join(sha(f.read_bytes())+' '+f.relative_to(out).as_posix()+'\n' for f in files),'utf-8')
- with zipfile.ZipFile(archive,'x',zipfile.ZIP_DEFLATED) as z:
- for f in sorted(out.rglob('*')):
- if f.is_file():z.write(f,f.relative_to(out).as_posix())
- print(json.dumps({'ok':True,'archive':str(archive),'bytes':archive.stat().st_size,'sha256':sha(archive.read_bytes()),'productionDeployed':False,'paidCalls':0}))
- if __name__=='__main__':
- p=argparse.ArgumentParser(description=__doc__);p.add_argument('--out',type=Path,required=True);p.add_argument('--seal',action='store_true')
- a=p.parse_args();(seal if a.seal else build)(a.out.resolve())
|