| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- """Build r6 from the immutable r5 release plus explicit request-guard changes only."""
- import argparse
- import difflib
- 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-r5/package'
- WORK = ROOT / 'work/industry-monitoring-20261107-r1'
- RELEASE = 'ksf-industry-monitor-20261107-r6'
- INTEGRATION = """ from monitoring_dispatch import projection as dispatch_projection
- pipeline['requestGuard'] = dispatch_projection(self.task_store, self.data['generation'])
- """
- def sha(raw):
- return hashlib.sha256(raw).hexdigest()
- def main():
- p = argparse.ArgumentParser(description=__doc__)
- p.add_argument('--out', required=True, type=Path)
- p.add_argument('--seal', action='store_true', help='Seal an accepted candidate after receipts are written')
- args = p.parse_args()
- out = args.out.resolve()
- if args.seal:
- release = json.loads((out / 'RELEASE.json').read_text('utf-8'))
- receipt = json.loads((out / 'evidence/implementation-receipt-v6.json').read_text('utf-8'))
- if release['release'] != RELEASE or not receipt.get('browserPassed') or receipt.get('tests', {}).get('python') != 61:
- 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')
- files = sorted(path for path in out.rglob('*') if path.is_file() and path != out / 'FILES.sha256')
- (out / 'FILES.sha256').write_text(''.join(sha(path.read_bytes()) + ' ' + path.relative_to(out).as_posix() + '\n' for path 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()), 'productionDeployed': False, 'paidCalls': 0}, ensure_ascii=False))
- return
- if out.exists():
- raise ValueError('RELEASE_DIRECTORY_MUST_BE_NEW')
- # Do not copy unrelated work-in-progress innovation views or shared module changes.
- 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('R5_FROZEN_BASELINE_CHANGED')
- shutil.copytree(BASE, out)
- before = (BASE / 'server/voc-library/industry_monitor.py').read_text('utf-8')
- anchor = " self.task_query.get('pipelinePage', 1), self.task_query.get('pipelineRevision'))\n"
- if before.count(anchor) != 1:
- raise ValueError('R5_INTEGRATION_ANCHOR_CHANGED')
- updated = before.replace(anchor, anchor + INTEGRATION)
- (out / 'server/voc-library/industry_monitor.py').write_text(updated, 'utf-8')
- names = ['server/voc-library/monitoring_dispatch.py', 'server/voc-library/test_monitoring_dispatch.py',
- 'scripts/manage-industry-dispatch.py']
- for name in names:
- shutil.copyfile(ROOT / name, out / name)
- shutil.copyfile(ROOT / 'src/pages/industry-pipeline-view.js', out / 'payload/src/pages/industry-pipeline-view.js')
- # Verify the only UI change to the frozen module is the guard section.
- old_ui = (BASE / 'payload/src/pages/industry-pipeline-view.js').read_text('utf-8')
- new_ui = (out / 'payload/src/pages/industry-pipeline-view.js').read_text('utf-8')
- lines = [line for line in new_ui.splitlines(True) if 'class="request-guard"' not in line]
- if ''.join(lines) != old_ui:
- raise ValueError('UNRELATED_PIPELINE_UI_CHANGES')
- with (out / 'evidence/ui.patch').open('a', encoding='utf-8') as stream:
- stream.write(''.join(difflib.unified_diff(old_ui.splitlines(True), new_ui.splitlines(True),
- fromfile='r5/src/pages/industry-pipeline-view.js',
- tofile='r6/src/pages/industry-pipeline-view.js')))
- shutil.copyfile(ROOT / 'docs/industry-monitoring-20261107/request-guard.md', out / 'docs/request-guard.md')
- for name in ('dispatch-closeout-import.json', 'dispatch-paid-replay-verification.json'):
- shutil.copyfile(WORK / name, out / 'evidence' / name)
- # Auxiliary working copy only. The active research snapshot is never packaged or altered.
- dest = out / 'data/monitoring-registry/monitoring.sqlite'
- with sqlite3.connect((WORK / 'monitoring-registry/monitoring.sqlite').resolve().as_uri() + '?mode=ro', uri=True) as source:
- with sqlite3.connect(dest) as c:
- source.backup(c)
- c.execute("UPDATE pipeline_workers SET state='ARCHIVED_COPY'")
- c.execute("UPDATE dispatch_audits SET state='ARCHIVED_COPY'")
- c.commit()
- for file in (WORK / 'monitoring-registry/assets').iterdir():
- target = out / 'data/monitoring-registry/assets' / file.name
- if not target.exists():
- shutil.copyfile(file, target)
- # Refresh release identity and the publisher's source-byte manifest; preserve all public baselines.
- manifest_path = out / 'deploy/industry-ui-files.json'
- manifest = json.loads(manifest_path.read_text('utf-8'))
- manifest['release'] = RELEASE
- for entry in manifest['files']:
- entry['sha256'] = sha((out / 'payload' / entry['path']).read_bytes())
- manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n', 'utf-8')
- publisher = out / 'scripts/publish-industry-ui.mjs'
- publisher.write_text(publisher.read_text('utf-8').replace('ksf-industry-monitor-20261107-r5', RELEASE), 'utf-8')
- for directory in ('docs', 'deploy'):
- for path in (out / directory).iterdir():
- if path.is_file() and path.suffix in ('.md', '.example'):
- path.write_text(path.read_text('utf-8-sig').replace('ksf-industry-monitor-20261107-r5', RELEASE), 'utf-8')
- with (out / 'docs/README.md').open('a', encoding='utf-8') as stream:
- stream.write('\n\n## r6:请求前核对与认领\n\n新增已结算76条请求保护、批次查重、并发核对认领和停费状态投影;操作见 [request-guard.md](request-guard.md)。上游执行端尚未接入,收费保持停止。本包仅在已冻结r5上追加本轮变更。\n')
- release_path = out / 'RELEASE.json'
- release = json.loads(release_path.read_text('utf-8'))
- release.update(release=RELEASE, createdAt=datetime.now(timezone.utc).isoformat(),
- builtFromFrozenRelease='ksf-industry-monitor-20261107-r5',
- requestGuardImplemented=True, externalCollectorsIntegrated=False,
- newPaidCalls=0, settledCloseoutIdentifiedRows=76, overallComplete=False)
- release['backendFiles'].append('server/voc-library/monitoring_dispatch.py')
- release_path.write_text(json.dumps(release, ensure_ascii=False, indent=2) + '\n', 'utf-8')
- # Browser acceptance and final test receipts are added before sealing, never after ZIP creation.
- print(json.dumps({'ok': True, 'candidate': str(out), 'sealed': False, 'paidCalls': 0}, ensure_ascii=False))
- if __name__ == '__main__':
- main()
|