| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- """Freeze an explicit runtime whitelist; exclude databases, secrets and business binaries."""
- import argparse
- import ast
- import hashlib
- import json
- from pathlib import Path
- import zipfile
- ROOT = Path(__file__).resolve().parents[1]
- NAME = 'ksf-unified-database-migration-20261109-r3'
- MODULES = '''agent_batch board_library industry_decisions industry_innovation industry_monitor
- industry_policy industry_research industry_workshop intake_contract library_audit library_channel
- library_receiver monitoring_details monitoring_dispatch monitoring_evidence monitoring_inbox
- monitoring_ledger monitoring_pipeline monitoring_policy monitoring_store monitoring_tasks
- policy_publications policy_usage_review product_insights query_voc ranking_discovery retrieval
- review_correction schema_catalog structured_batch unified_store unified_worker update_library voc_db'''.split()
- COMMANDS = '''migrate-unified-database verify-unified-parity install-unified-skills
- manage-industry-pipeline manage-industry-tasks manage-industry-dispatch register-industry-evidence
- register-industry-details register-industry-policy register-industry-ranking-identities
- check-monitoring-evidence compare-industry-rankings discover-industry-rankings
- prepare-industry-inbox export-industry-snapshot'''.split()
- def sha(raw):
- return hashlib.sha256(raw).hexdigest()
- def main():
- p = argparse.ArgumentParser(description=__doc__)
- p.add_argument('--out', required=True, type=Path, help='New delivery directory')
- a = p.parse_args()
- assert not a.out.exists(), 'NEW_DELIVERY_DIRECTORY_REQUIRED'
- evidence = ROOT / 'work/single-database-20261109'
- for name in ('unified-test-result.json', 'regression-result.json', 'http-test-result.json'):
- assert json.loads((evidence / name).read_text('utf-8'))['success'], name
- assert json.loads((evidence / 'cli-full/04-parity.json').read_text('utf-8'))['ok']
- assert json.loads((evidence / 'component-installer-result-r2.json').read_text('utf-8'))['success']
- assert json.loads((evidence / 'r3/validation.json').read_text('utf-8'))['success']
- files = {}
- def copy(source, target=None):
- files[target or source] = (ROOT / source).read_bytes()
- for n in MODULES:
- copy('server/voc-library/' + n + '.py')
- for n in ('schema.sql', 'taxonomy.json'):
- copy('server/voc-library/' + n)
- for n in ('library-api', 'industry-api', 'industry-service', 'unified-worker'):
- copy('server/' + n + '.mjs')
- for n in COMMANDS:
- copy('scripts/' + n + '.py')
- copy('docs/unified-database-migration-20261109/执行单.md', '执行单.md')
- copy('docs/unified-database-migration-20261109/本地验收.md', '本地验收.md')
- copy('docs/unified-database-migration-20261109/unified-db.example.json', 'deploy/unified-db.example.json')
- copy('docs/unified-database-migration-20261109/requirements-unified.txt', 'requirements-unified.txt')
- protected = []
- for skill in ('ksf-voc-library', 'ksf-workshop-runtime'):
- base = ROOT / 'skills' / skill
- component_manifest = {}
- for line in (base / 'FILES.sha256').read_text('utf-8').splitlines():
- digest, name = line.split(' ', 1)
- assert sha((base / name).read_bytes()) == digest, (skill, name)
- if skill == 'ksf-voc-library':
- if name.startswith('scripts/'):
- copy('skills/' + skill + '/' + name, 'components/research-runtime/' + name)
- component_manifest[name] = digest
- continue
- key = 'skills/' + skill + '/' + name
- if skill == 'ksf-workshop-runtime' and (name.startswith('scripts/') or name == 'runtime-lock.json'):
- protected.append({'path': name, 'sha256': digest})
- continue
- copy(key)
- if skill == 'ksf-voc-library':
- files['components/research-runtime/FILES.sha256'] = ''.join(h + ' ' + n + '\n' for n, h in sorted(component_manifest.items())).encode('utf-8')
- else:
- copy('skills/' + skill + '/FILES.sha256')
- assert len(protected) == 7
- checks = ['unified-test-result.json', 'regression-result.json', 'http-test-result.json',
- 'workshop-test-result.json', 'skill-static-verification-r2.json', 'component-installer-result-r2.json',
- 'cli-full/01-plan/migration-receipt.json', 'cli-full/02-import/migration-receipt.json',
- 'cli-full/03-verify/migration-receipt.json', 'cli-full/04-parity.json', 'runtime-measurement.json']
- for name in checks:
- copy('work/single-database-20261109/' + name, 'evidence/history-r2/' + name)
- for name in ('validation.json', 'parity.json', 'plan-final/migration-receipt.json', 'apply-final/migration-receipt.json', 'verify-final/migration-receipt.json', 'store-tests.log', 'installer-probe-tests.log', 'http-node-tests.log', 'rehearsal-corrections.json', 'receiver-regression.log', 'receiver-offline-service-tests.log', 'skill-static-verification.json'):
- copy('work/single-database-20261109/r3/' + name, 'evidence/r3/' + name)
- copy('docs/unified-database-migration-20261109/operations-readonly-identity-confirmed-r3.json', 'evidence/r3/operations-readonly-identity-confirmed.json')
- # Provenance hashes do not substitute for the separately shipped operational checks.
- for name, body in files.items():
- assert not name.endswith(('.sqlite', '.zip', '.env', '.pem', '.key'))
- if name.endswith('.py'):
- ast.parse(body.decode('utf-8-sig'), filename=name, feature_version=(3, 9))
- release = {'release': NAME, 'schema': 'ksf.unified-db.v1', 'database': 'FKieRbIoGt_master', 'role': 'FKieRbIoGt_admin',
- 'appId': 'FKieRbIoGt_main', 'supersedes': 'ksf-unified-database-migration-20261109-r2', 'strictTargetGuardRetained': True,
- 'workshopSkillVersion': 'r9', 'productionDeployed': False, 'sourceDataIncluded': False,
- 'connectionCredentialsIncluded': False, 'businessBinariesIncluded': False,
- 'protectedInstalledBusinessFiles': protected, 'payloadFiles': len(files),
- 'workshopSkillEntrypoints': ['ksf-workshop-runtime'],
- 'researchRuntimeComponent': 'components/research-runtime',
- 'legacyResearchSkillEntryDistributed': False,
- 'localValidationScope': 'isolated PostgreSQL; older local library; production acceptance pending'}
- entries = [n for n in files if n.endswith('/SKILL.md')]
- assert entries == ['skills/ksf-workshop-runtime/SKILL.md'], entries
- files['release.json'] = (json.dumps(release, ensure_ascii=False, indent=2) + '\n').encode('utf-8')
- files['FILES.sha256'] = ''.join(sha(body) + ' ' + name + '\n' for name, body in sorted(files.items())).encode('utf-8')
- a.out.mkdir(parents=True)
- package = a.out / 'package'
- for name, raw in files.items():
- path = package / name;path.parent.mkdir(parents=True, exist_ok=True);path.write_bytes(raw)
- archive = a.out / (NAME + '.zip')
- with zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) as z:
- for name, raw in sorted(files.items()):
- z.writestr(name, raw)
- with zipfile.ZipFile(archive) as z:
- assert z.testzip() is None
- assert set(z.namelist()) == set(files)
- for line in z.read('FILES.sha256').decode('utf-8').splitlines():
- digest, name = line.split(' ', 1)
- assert sha(z.read(name)) == digest
- receipt = {**release, 'zip': str(archive.absolute()), 'sha256': sha(archive.read_bytes()),
- 'bytes': archive.stat().st_size, 'entriesVerified': len(files)}
- (a.out / 'delivery-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
- print(json.dumps(receipt, ensure_ascii=False))
- if __name__ == '__main__':
- main()
|