| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- """Build one frozen r8 containing innovation, detail evidence, pipeline and request guard."""
- import argparse
- from datetime import datetime, timezone
- import difflib
- 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-r7/package'
- WORK = ROOT / 'work/industry-monitoring-20261107-r1'
- RELEASE = 'ksf-industry-monitor-20261107-r8'
- SOURCES = [
- 'src/pages/view-industry.js', 'src/pages/industry-innovation-view.js', 'src/pages/industry-detail-view.js',
- 'src/core/industry-brief.js', 'src/core/industry-materials.js', 'src/services/industry-service.js',
- 'server/voc-library/industry_monitor.py', 'server/voc-library/industry_innovation.py',
- 'server/voc-library/monitoring_details.py', 'server/voc-library/test_industry_innovation.py',
- 'server/voc-library/test_monitoring_details.py', 'test/industry-brief.test.js',
- 'scripts/verify-industry-install.py', 'scripts/prepare-industry-handoff.py',
- 'scripts/verify-industry-monitor.py', 'scripts/verify-industry-mainline.py',
- 'scripts/verify-industry-mainline-release.py',
- ]
- def sha(raw):
- return hashlib.sha256(raw).hexdigest()
- def save_json(path, value):
- 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_R7_CHANGED')
- # Capture only the named modules; shared navigation and core VOC remain frozen r7.
- captured = {name: (ROOT / name).read_bytes() for name in SOURCES}
- shutil.copytree(BASE, out)
- patch = []
- for name, raw in captured.items():
- target_name = ('payload/' + name) if name.startswith('src/') else name
- target = out / target_name
- target.parent.mkdir(parents=True, exist_ok=True)
- before = target.read_text('utf-8') if target.is_file() else ''
- after = raw.decode('utf-8')
- target.write_bytes(raw)
- patch.extend(difflib.unified_diff(before.splitlines(True), after.splitlines(True),
- fromfile='r7/' + target_name, tofile='r8/' + target_name))
- # Test imports follow the deployable payload layout inside a release archive.
- test_path = out / 'test/industry-brief.test.js'
- test_path.write_text(test_path.read_text('utf-8').replace('../src/', '../payload/src/'), 'utf-8')
- if any((ROOT / name).read_bytes() != raw for name, raw in captured.items()):
- raise ValueError('SOURCE_CHANGED_DURING_FREEZE')
- save_json(out / 'evidence/r8-source-capture.json', {'release': RELEASE,
- 'sourceFilesSha256': {name: sha(raw) for name, raw in captured.items()},
- 'builtFromFrozenRelease': 'ksf-industry-monitor-20261107-r7'})
- (out / 'evidence/r8-integration.patch').write_text(''.join(patch), 'utf-8')
- handoff = ROOT / 'work/industry-mainline-handoff-20261107-r1'
- shutil.copytree(handoff, out / 'handoff')
- for name in ('detail-endpoint-proof-receipt.json',):
- shutil.copyfile(WORK / name, out / 'evidence' / name)
- for name in ('发给VOC采集方-只读核查与报价.md', '主线待决策清单.md', '运维001-r8上线执行单.md', 'r8交付与待接通事项.md'):
- shutil.copyfile(ROOT / 'docs/industry-monitoring-20261107' / name, out / 'docs' / name)
- # A fresh auxiliary copy carries existing history plus the appended endpoint provenance.
- auxiliary = WORK / 'monitoring-registry'
- with sqlite3.connect((auxiliary / 'monitoring.sqlite').resolve().as_uri() + '?mode=ro', uri=True) as c:
- with sqlite3.connect(out / 'data/monitoring-registry/monitoring.sqlite') as dest:
- c.backup(dest)
- dest.execute("UPDATE pipeline_workers SET state='ARCHIVED_COPY'")
- dest.execute("UPDATE dispatch_audits SET state='ARCHIVED_COPY'")
- dest.commit()
- for source in (auxiliary / 'assets').iterdir():
- target = out / 'data/monitoring-registry/assets' / source.name
- if not target.exists():
- shutil.copyfile(source, target)
- if list(out.rglob('detail-originals')):
- raise ValueError('PRIVATE_ORIGINALS_MUST_NOT_BE_INCLUDED')
- release = json.loads((out / 'RELEASE.json').read_bytes())
- release.update(release=RELEASE, createdAt=datetime.now(timezone.utc).isoformat(),
- builtFromFrozenRelease='ksf-industry-monitor-20261107-r7',
- innovationWorkspaceImplemented=True, agentCopyMaterialsImplemented=True,
- unifiedMainlinePackage=True, overallComplete=False,
- nativePython39Verified=False, productionDeployed=False, paidCalls=0)
- for name in ('src/pages/industry-innovation-view.js', 'src/core/industry-brief.js', 'src/core/industry-materials.js'):
- if name not in release['staticFiles']:
- release['staticFiles'].append(name)
- if 'server/voc-library/industry_innovation.py' not in release['backendFiles']:
- release['backendFiles'].append('server/voc-library/industry_innovation.py')
- save_json(out / 'RELEASE.json', release)
- path = out / 'deploy/industry-ui-files.json'
- manifest = json.loads(path.read_bytes())
- manifest['release'] = RELEASE
- for name in release['staticFiles']:
- if not any(e['path'] == name for e in manifest['files']):
- manifest['files'].append({'path': name, 'allowMissing': True,
- 'priorNormalizedSha256': [], 'observedProductionSha256': None})
- for entry in manifest['files']:
- previous = BASE / 'payload' / entry['path']
- if previous.exists():
- old_sha = sha(previous.read_bytes().replace(b'\r\n', b'\n'))
- if old_sha not in entry['priorNormalizedSha256']:
- entry['priorNormalizedSha256'].append(old_sha)
- entry['sha256'] = sha((out / 'payload' / entry['path']).read_bytes().replace(b'\r\n', b'\n'))
- save_json(path, manifest)
- publisher = out / 'scripts/publish-industry-ui.mjs'
- script = publisher.read_text('utf-8').replace('ksf-industry-monitor-20261107-r7', RELEASE)
- old = next(line for line in script.splitlines() if line.startswith('export const FILES='))
- script = script.replace(old, 'export const FILES=Object.freeze(' + json.dumps(release['staticFiles']) + ');')
- publisher.write_text(script, 'utf-8')
- for path in (out / 'deploy').iterdir():
- if path.suffix == '.example':
- path.write_text(path.read_text('utf-8').replace('ksf-industry-monitor-20261107-r7', RELEASE), 'utf-8')
- (out / 'docs/README.md').write_text('''# 方便面行业VOC后台统一r8交付
- 本包整合创新分析工作区、员工复制给Agent的分析资料、详情原文证据、评价/政策/榜单查询、分页接收与停费核对。已实现与本地可验证的功能统一交付;真实全量采集、上游持续调度、商品全榜和生产发布仍按回执分别验收。
- 请先读[运维001-r8上线执行单.md](运维001-r8上线执行单.md)。采集恢复前只读核查见[发给VOC采集方-只读核查与报价.md](发给VOC采集方-只读核查与报价.md),精确范围在handoff/collector-scope.json。
- 历史r4-r7文档和回执保留作来源,旧文件数与版本路径不作为r8执行指令。r8实际前端13项,后端13项,以RELEASE.json和deploy清单为准。不存在的接口不包装成已可调用;本轮无新增收费,无生产写入。包内原始研究库、凭据与私有完整详情原件均不包含。辅助SQLite副本不得覆盖现有工作台账。
- 一次分析固定当前研究代际。默认主店京东1000084104;新旧快照不混用,不重跑A/B/C,不修改正常合并规则。完整性只对证实的接口可访问范围成立,不把首页样本、正文可读或详情徽标说成全量或合格分析样本。
- ''', 'utf-8')
- print(json.dumps({'ok': True, 'candidate': str(out), 'staticFiles': len(release['staticFiles']),
- 'backendFiles': len(release['backendFiles']), 'sealed': False, 'paidCalls': 0}))
- def seal(out):
- receipt = json.loads((out / 'evidence/implementation-receipt-v8.json').read_bytes())
- if not receipt.get('accepted') or not receipt.get('packageVerificationPassed'):
- raise ValueError('CANDIDATE_ACCEPTANCE_REQUIRED')
- for name, digest in receipt['testedFilesSha256'].items():
- if sha((out / name).read_bytes()) != digest:
- raise ValueError('TESTED_SOURCE_CHANGED')
- archive = out.parent / (RELEASE + '.zip')
- if archive.exists():
- raise ValueError('SEALED_ARCHIVE_ALREADY_EXISTS')
- # Preserve nested historical manifests; only the root manifest is rebuilt.
- files = sorted(p for p in out.rglob('*') if p.is_file() and p != out / 'FILES.sha256')
- (out / 'FILES.sha256').write_text(''.join(sha(p.read_bytes()) + ' ' + p.relative_to(out).as_posix() + '\n' for p in files), 'utf-8')
- with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED) as z:
- for path in sorted(out.rglob('*')):
- if path.is_file():
- z.write(path, path.relative_to(out).as_posix())
- print(json.dumps({'ok': True, 'archive': str(archive), 'bytes': archive.stat().st_size,
- 'sha256': sha(archive.read_bytes()), 'paidCalls': 0, 'productionDeployed': False}))
- if __name__ == '__main__':
- p = argparse.ArgumentParser(description=__doc__)
- p.add_argument('--out', type=Path, required=True)
- p.add_argument('--seal', action='store_true')
- args = p.parse_args()
- (seal if args.seal else build)(args.out.resolve())
|