| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- """Build the r7 instructions update against the reconstructed, sealed r6 baseline."""
- from pathlib import Path
- import argparse
- import hashlib
- import importlib.util
- import json
- import re
- import zipfile
- ROOT = Path(__file__).resolve().parents[1]
- OUT = ROOT / 'docs/workshop-skill-release-20261109-r7'
- NAME = 'ksf-workshop-skill-update-20261109-r7'
- spec = importlib.util.spec_from_file_location('r6_packager', ROOT / 'scripts/package-workshop-intake-update.py')
- previous = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(previous)
- ARCHIVES = previous.ARCHIVES + [
- ('r6', 'docs/workshop-skill-release-20261109-r6/ksf-workshop-skill-update-20261109-r6.zip',
- 'c329432b9773cfb0a8e5d4a3e5c162b3778eab28d576b79875da6854f33c4dc5'),
- ]
- sha = lambda b: hashlib.sha256(b).hexdigest()
- encode = lambda v: (json.dumps(v, ensure_ascii=False, indent=2) + '\n').encode('utf-8')
- def stage():
- bodies = {}
- for version, relative, digest in ARCHIVES:
- archive = ROOT / relative
- assert sha(archive.read_bytes()) == digest, version
- with zipfile.ZipFile(archive) as z:
- assert z.testzip() is None
- meta = json.loads(z.read('release.json'))
- prefix = 'skill/' if version == 'r2' else 'payload/'
- bodies = {n: z.read(prefix + n) if prefix + n in z.namelist() else bodies[n]
- for n in meta['candidate']}
- assert all(sha(bodies[n]) == h for n, h in meta['candidate'].items()), version
- baseline = meta['candidate']
- assert len(baseline) == 42
- assert baseline['SKILL.md'] == '3cb64f381937411b0a2b853772bb19d37f44528a2902a89ef475b707472d3473'
- for n, raw in bodies.items():
- p = OUT / 'rehearsal-baseline-r6' / n
- p.parent.mkdir(parents=True, exist_ok=True)
- p.write_bytes(raw)
- skill = ROOT / 'skills/ksf-workshop-runtime'
- candidate = {}
- for line in (skill / 'FILES.sha256').read_text(encoding='utf-8').splitlines():
- digest, n = line.split(' ', 1)
- assert re.fullmatch(r'[a-f0-9]{64}', digest)
- assert re.fullmatch(r'[A-Za-z0-9_./-]+', n) and '..' not in Path(n).parts
- assert not Path(n).is_absolute() and n not in candidate
- assert sha((skill / n).read_bytes()) == digest, n
- candidate[n] = digest
- candidate['FILES.sha256'] = sha((skill / 'FILES.sha256').read_bytes())
- assert len(candidate) == 43
- assert candidate['SKILL.md'] == '15b0bd8be52bd40dbcb040ff2d532e8d637c1ee09305c19f07b3e1c28efaf2e5'
- assert candidate['FILES.sha256'] == '48686b72c78fee7bb5bfd3c2589744d41a14070cd7875cedae58c9d3441601b1'
- delta = json.loads((OUT / 'planned-paths.json').read_bytes())
- assert len(delta) == len(set(delta)) == 23
- assert not set(baseline) - set(candidate)
- assert {n for n in candidate if baseline.get(n) != candidate[n]} == set(delta)
- assert set(candidate) - set(baseline) == {'references/workshop-report-design.md'}
- runtime = [n for n in baseline if n == 'runtime-lock.json' or n.startswith('scripts/')]
- assert len(runtime) == 7 and all(candidate[n] == baseline[n] for n in runtime)
- links = 0
- markdown = []
- for n in candidate:
- if not n.endswith('.md'):
- continue
- p = skill / n
- text = p.read_text(encoding='utf-8')
- assert not re.search('豆包|付费|收费|零费用|免费|计费', text), n
- markdown.append(text)
- for target in re.findall(r'\]\(([^)]+)\)', text):
- if '://' in target or target.startswith('#'):
- continue
- resolved = (p.parent / target.split('#', 1)[0]).resolve()
- assert resolved.is_relative_to(skill.resolve()) and resolved.is_file(), (n, target)
- links += 1
- stages = ['D1-01', 'D1-02', 'D1-03', 'D1-04', 'D1-N'] + [f'D2-{i:02}' for i in range(1, 9)]
- for n in ['references/workshop-intake.md', 'references/workshop-course-flow.md']:
- text = (skill / n).read_text(encoding='utf-8')
- assert all(s in text for s in stages), n
- refs = [n for n in candidate if n.startswith('references/')]
- assert len(refs) == 34 and sum(n.endswith('.md') for n in refs) == 32
- plan = [{'path': n, 'action': 'update' if n in baseline else 'create',
- 'beforeSha256': baseline.get(n), 'afterSha256': candidate[n]} for n in delta]
- release = {'schema': 'ksf.workshop-research-update.v1', 'releaseId': NAME,
- 'baselines': {'r6': baseline}, 'candidate': candidate,
- 'baselineArchiveSha256': {'r6': ARCHIVES[-1][2]},
- 'researchEntryNames': meta['researchEntryNames'], 'researchRuntimePins': meta['researchRuntimePins'],
- 'plans': {'r6': plan}, 'runtimeReplacementAllowed': False,
- 'businessWritesDuringUpgrade': False, 'serviceRestartAllowed': False, 'credentialsIncluded': False}
- integrity = {'ok': True, 'skillSha256': candidate['SKILL.md'], 'manifestSha256': candidate['FILES.sha256'],
- 'manifestEntries': 42, 'publicFiles': 43, 'relativeLinks': links,
- 'references': {'all': 34, 'md': 32, 'json': 2},
- 'runtimeFilesUnchanged': 7, 'courseModuleIdsPresent': stages,
- 'baseline': 'complete sealed r6', 'changedFiles': 23, 'updates': 22, 'creates': 1}
- sources = [
- 'docs/workshop-report-design-20261109/reference-review.md',
- 'docs/workshop-report-design-20261109/local-acceptance.json',
- 'docs/workshop-report-design-20261109/browser-checks.json',
- 'docs/workshop-report-design-20261109/拌面研究报告形式样稿.html',
- 'docs/workshop-live-case-r5-20261109/rounds/R06/local-skill-review/receipt.json',
- 'docs/workshop-live-case-r5-20261109/rounds/R07/local-skill-review/receipt.json',
- ]
- design = json.loads((ROOT / sources[1]).read_bytes())
- browser = json.loads((ROOT / sources[2]).read_bytes())
- assert design['ok'] and browser['ok'] and browser['renderChecks'] == 6
- assert sha((ROOT / sources[3]).read_bytes()) == design['sampleSha256']
- assert sha((ROOT / sources[2]).read_bytes()) == design['browserSha256']
- latest = json.loads((ROOT / sources[-1]).read_bytes())
- assert latest['ok'] and latest['manifestSha256'] == candidate['FILES.sha256']
- review = json.loads((OUT / 'content-review.json').read_bytes())
- assert review['ok'] and review['skillSha256'] == candidate['SKILL.md']
- assert review['manifestSha256'] == candidate['FILES.sha256']
- local = {'schema': 'ksf.report-update-local-review.v1', 'ok': True,
- 'skillSha256': candidate['SKILL.md'], 'manifestSha256': candidate['FILES.sha256'],
- 'evidence': [{'path': p, 'sha256': sha((ROOT / p).read_bytes())} for p in sources],
- 'reportExampleBrowserChecks': 6, 'reportExampleViewports': browser['viewports'],
- 'contentReview': review,
- 'scope': 'local instruction integrity, manual reasoning review and report example presentation; not live agent acceptance',
- 'remoteInstalled': False, 'newResearchExecuted': False, 'employeeUsefulnessValidated': False}
- files = {'payload/' + n: (skill / n).read_bytes() for n in delta}
- files.update({'release.json': encode(release),
- 'update-skill.mjs': (ROOT / 'scripts/update-workshop-report.mjs').read_bytes(),
- 'run-update.py': (ROOT / 'scripts/run-workshop-report-update.py').read_bytes(),
- 'README.md': (OUT / 'README.md').read_bytes(), 'CHANGES.md': (OUT / 'CHANGES.md').read_bytes(),
- 'skill-integrity.json': encode(integrity), 'local-acceptance.json': encode(local)})
- validation = OUT / 'installer-validation.json'
- if validation.exists():
- value = json.loads(validation.read_bytes())
- assert value['ok'] and value['updaterSha256'] == sha(files['update-skill.mjs'])
- assert value['runnerSha256'] == sha(files['run-update.py'])
- files[validation.name] = validation.read_bytes()
- files['FILES.sha256'] = ''.join(sha(raw) + ' ' + n + '\n' for n, raw in sorted(files.items())).encode()
- for n, raw in files.items():
- assert not any(t in n.lower() for t in ['credential-binding', 'private.pem', 'envelope', 'channel-config'])
- p = OUT / 'package' / n
- p.parent.mkdir(parents=True, exist_ok=True)
- p.write_bytes(raw)
- (OUT / 'skill-integrity.json').write_bytes(encode(integrity))
- (OUT / 'local-acceptance.json').write_bytes(encode(local))
- return files, release
- def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--pack', action='store_true')
- args = parser.parse_args()
- files, release = stage()
- if not args.pack:
- print(json.dumps({'ok': True, 'stagedFiles': len(files), 'baseline': 'r6', 'changes': 23}))
- return
- assert 'installer-validation.json' in files, 'Installer validation required before packaging'
- target = OUT / (NAME + '.zip')
- assert not target.exists(), 'Never overwrite a released ZIP'
- with zipfile.ZipFile(target, 'x', zipfile.ZIP_DEFLATED) as z:
- for n, raw in sorted(files.items()):
- info = zipfile.ZipInfo(n, (2026, 11, 9, 0, 0, 0))
- info.external_attr = 0o100600 << 16
- z.writestr(info, raw, compress_type=zipfile.ZIP_DEFLATED)
- with zipfile.ZipFile(target) as z:
- assert z.testzip() is None and set(z.namelist()) == set(files)
- assert all(z.read(n) == raw for n, raw in files.items())
- receipt = {'ok': True, 'releaseId': NAME, 'filename': target.name, 'bytes': target.stat().st_size,
- 'sha256': sha(target.read_bytes()), 'packageEntries': len(files),
- 'outerManifestEntries': len(files) - 1, 'payloadFiles': 23, 'targetPublicFiles': 43,
- 'plans': {'r6': {'updates': 22, 'creates': 1}},
- 'candidateSkillSha256': release['candidate']['SKILL.md'],
- 'candidateManifestSha256': release['candidate']['FILES.sha256'],
- 'runtimeFilesChanged': 0, 'credentialsIncluded': False,
- 'remoteInstalled': False, 'productionDatabaseWrites': 0}
- (OUT / 'package-receipt.json').write_bytes(encode(receipt))
- print(json.dumps(receipt, ensure_ascii=False, indent=2))
- if __name__ == '__main__':
- main()
|