| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- """Content traceability and real isolated installer verification. No production mutations."""
- import hashlib
- import importlib.util
- import json
- import re
- import shutil
- import tempfile
- import zipfile
- from unittest.mock import patch
- from html.parser import HTMLParser
- from pathlib import Path
- from urllib.parse import urlsplit,unquote
- ROOT=Path(__file__).resolve().parents[1]
- OUT=ROOT/'docs/workshop-quality-r13-20261110'
- BASE=ROOT/'docs/workshop-validated-final-20261110'
- sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
- class Links(HTMLParser):
- def __init__(self):super().__init__();self.links=[];self.ids=set()
- def handle_starttag(self,tag,attrs):
- d=dict(attrs)
- if 'id' in d:self.ids.add(d['id'])
- if tag=='a' and 'href' in d:self.links.append(d['href'])
- def run():
- receipts=sorted((OUT/'data').glob('*.request.json'))
- assert len(receipts)==37,len(receipts)
- gen=json.loads((OUT/'data/version.json').read_bytes())['generation']
- for p in receipts:
- r=json.loads(p.read_bytes());assert r['readOnly'] is True and r['databaseWrites']==0 and r['ok']
- assert r['generation']==gen
- assert sha(p.with_name(p.name.replace('.request.json','.json')))==r['responseSha256']
- d=json.loads((OUT/'证据与编码-r13.json').read_bytes())
- old=json.loads((ROOT/'docs/workshop-self-run-20261110/证据与编码.json').read_bytes())
- assert d['oldReviews']==old['reviews'] and d['oldProducts']==old['products'] and d['social']==old['social']
- keys=set()
- for r in d['addedReviews']:
- raw=json.loads((OUT/r['responseFile']).read_bytes())['record']
- assert (r['recordId'],r['quote'],r['productId'],r['purchasedSku'])==(raw['id'],raw['content'],raw['product_id'],raw['purchased_sku_text'])
- assert r['recordId'] not in {x['recordId'] for x in old['reviews']}
- keys.add(r['recordId'])
- assert len(keys)==18
- usable=[r for r in d['addedReviews'] if r['status']=='usable-with-scope']
- assert len(usable)==14 and len({r['quote'].strip() for r in usable})==14
- assert [r['key'] for r in d['addedReviews'] if r['status']=='pending']==['R06','R08','R10']
- assert [r['key'] for r in d['addedReviews'] if r['status']=='excluded']==['R17']
- for s in d['statistics']:
- assert s['denominatorMembers']==[r['key'] for r in usable]
- assert s['members']==[r['key'] for r in usable if s['theme'] in r['tags']]
- assert s['N']==14 and s['n']==len(s['members']) and s['percent']==round(100*s['n']/14,1)
- assert s['sensitivityN']==12 and s['sensitivityn']==len(set(s['members'])-{'R07','R12'})
- assert [(s['n'],s['N']) for s in d['statistics']]==[(7,14),(2,14),(3,14),(1,14)]
- assert '吃腻' in next(r['quote'] for r in d['addedReviews'] if r['key']=='R13')
- assert json.loads((OUT/'data/flavour-fatigue.json').read_bytes())['totalMatched']==0
- products={r['key']:r for r in d['addedProducts']}
- assert list(products)==['P08','P09','P10','P11']
- for r in products.values():
- raw=json.loads((OUT/r['responseFile']).read_bytes())['record']
- assert (r['recordId'],r['title'])==(raw['id'],raw['title'])
- for key,day,minor,count,value in [('P08','2026-11-06',4090,6,6.82),('P09','2026-11-06',5390,12,4.49),('P11','2026-11-06',5770,12,4.81),('P11','2026-11-03',5340,12,4.45)]:
- assert any(p['source_day']==day and p['amount_minor']==minor for p in products[key]['priceRows'])
- assert round(minor/100/count,2)==value
- pkg=OUT/'installation';meta=json.loads((pkg/'release.json').read_bytes())
- files=meta['workshop']['files'];assert len(files)==45
- skill=pkg/'skills/ksf-workshop-runtime'
- assert (skill/'references/workshop-report-design.md').read_text('utf8').count('## 分类型学习与内容验收')==1
- for n,h in files.items():assert sha(skill/n)==h and sha(ROOT/'skills/ksf-workshop-runtime'/n)==h,n
- protected=['runtime-lock.json']+['scripts/'+n for n in ['ksf.mjs','ksf-secure.mjs','ksf-workshop.mjs','ksf-identity.mjs','ksf-identity-bridge.mjs','describe-business-schema.mjs']]
- for n in protected:assert sha(skill/n)==sha(BASE/'installation/skills/ksf-workshop-runtime'/n),n
- for folder in [pkg,skill]:
- for line in (folder/'FILES.sha256').read_text('utf8').splitlines():
- h,n=line.split(' ',1);assert sha(folder/n)==h,n
- with zipfile.ZipFile(OUT/'康师傅工作坊Skill-r13本地修订.zip') as z:
- assert {n for n in z.namelist()}=={p.relative_to(pkg).as_posix() for p in pkg.rglob('*') if p.is_file()}
- assert all(z.read(n)==(pkg/n).read_bytes() for n in z.namelist())
- references=0
- for p in skill.rglob('*.md'):
- for _,link in re.findall(r'\[([^\]]+)\]\(([^)]+)\)',p.read_text('utf8')):
- if '://' in link or link.startswith('#'):continue
- assert (p.parent/link.split('#')[0]).is_file(),(p,link)
- references+=1
- spec=importlib.util.spec_from_file_location('isolated_r13_installer',pkg/'install.py')
- installer=importlib.util.module_from_spec(spec);spec.loader.exec_module(installer)
- fixtures=Path(tempfile.mkdtemp(prefix='r13-check-',dir=ROOT/'work/workshop-report-quality-20261110'))
- payload={'ksf-workshop-runtime':{n:(skill/n).read_bytes() for n in files}}
- target=fixtures/'exact/ksf-workshop-runtime'
- shutil.copytree(BASE/'installation/skills/ksf-workshop-runtime',target)
- for name in ['credential-binding.json','local-note.txt']:(target/name).write_text('synthetic-preservation-fixture\n',encoding='utf8')
- preserved={n:sha(target/n) for n in ['credential-binding.json','local-note.txt']}
- actions=installer.plan(target.parent,payload,meta['upgradeFrom']);assert actions[0]['action']=='upgrade'
- installer.install_files(target.parent,payload,actions,fixtures/'backup')
- installer.verify_component(target,payload['ksf-workshop-runtime'])
- assert all(sha(target/n)==h for n,h in preserved.items())
- assert installer.plan(target.parent,payload,meta['upgradeFrom'])[0]['action']=='verify-only'
- unknown=fixtures/'unknown/ksf-workshop-runtime';shutil.copytree(BASE/'installation/skills/ksf-workshop-runtime',unknown)
- (unknown/'references/workshop-feedback.md').write_text('unknown local edits',encoding='utf8')
- before={p.relative_to(unknown).as_posix():sha(p) for p in unknown.rglob('*') if p.is_file()}
- try:installer.plan(unknown.parent,payload,meta['upgradeFrom']);raise AssertionError('unknown baseline accepted')
- except installer.InstallError as e:assert str(e)=='UPGRADE_BASELINE_CHANGED'
- assert before=={p.relative_to(unknown).as_posix():sha(p) for p in unknown.rglob('*') if p.is_file()}
- assert installer.plan(fixtures/'new',payload,meta['upgradeFrom'])[0]['action']=='create'
- collision=fixtures/'collision/ksf-workshop-runtime';shutil.copytree(BASE/'installation/skills/ksf-workshop-runtime',collision)
- addition='references/workshop-deliverable-contracts.md'
- (collision/addition).write_text('unknown local reference',encoding='utf8')
- try:installer.plan(collision.parent,payload,meta['upgradeFrom']);raise AssertionError('new-file collision accepted')
- except installer.InstallError as e:assert str(e)=='UPGRADE_NEW_FILE_COLLISION'
- rollback=fixtures/'rollback/ksf-workshop-runtime';shutil.copytree(BASE/'installation/skills/ksf-workshop-runtime',rollback)
- before={p.relative_to(rollback).as_posix():sha(p) for p in rollback.rglob('*') if p.is_file()}
- actions=installer.plan(rollback.parent,payload,meta['upgradeFrom'])
- with patch.object(installer,'verify_component',side_effect=installer.InstallError('INJECTED_POST_UPGRADE_FAILURE')):
- try:installer.install_files(rollback.parent,payload,actions,fixtures/'rollback-backup');raise AssertionError('failure not injected')
- except installer.InstallError as e:assert str(e)=='INJECTED_POST_UPGRADE_FAILURE'
- assert before=={p.relative_to(rollback).as_posix():sha(p) for p in rollback.rglob('*') if p.is_file()}
- model=json.loads((OUT/'商业回推模型.json').read_bytes())
- assert all(x['value'] is None for x in model['fields'].values()) and not model['empiricalCompanyData']
- original=ROOT/'docs/workshop-final-local-20261110/康师傅工作坊操作手册-本地定稿.html'
- assert sha(original)=='8c8942c3d1331693570928b8f54300f13f635387cfa3dcf1422bde3cec1740ab'
- # All links and cross-document evidence anchors must resolve, including old citations.
- checked=0;cache={}
- for p in OUT.rglob('*.html'):
- parser=Links();parser.feed(p.read_text('utf8'))
- for href in parser.links:
- link=urlsplit(href)
- if link.scheme or link.netloc:continue
- dest=(p.parent/unquote(link.path)).resolve() if link.path else p.resolve()
- assert dest.is_file(),(p,href)
- dynamic_manual=dest.name.startswith('康师傅工作坊操作手册') or dest==OUT/'manual/index.html'
- if link.fragment and dest.suffix=='.html' and not dynamic_manual:
- if dest not in cache:
- scan=Links();scan.feed(dest.read_text('utf8'));cache[dest]=scan.ids
- assert unquote(link.fragment) in cache[dest],(p,href)
- checked+=1
- result={'ok':True,'actualReadOnlyRequests':len(receipts),'generation':gen,'productionWrites':0,
- 'old49Preserved':True,'new18BodiesExact':True,'denominatorsRecomputed':len(d['statistics']),
- 'newProductIndexVerified':4,'skillPublicFiles':len(files),'protected7Unchanged':True,
- 'skillReferenceLinks':references,'strictR12Upgrade':'actual isolated plan/apply/verify',
- 'unknownBaselineRejectedWithoutMutation':True,'localSyntheticFilesPreserved':True,
- 'newReferenceCollisionRejected':True,'injectedUpgradeFailureRolledBack':True,
- 'htmlLinksAndAnchors':checked,'empiricalCompanyModelInputs':False,
- 'independentModelReview':False,'realEmployeeTrial':False,'completeCourseEmpiricalAcceptance':False}
- (OUT/'内容与安装核验.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf8')
- print(json.dumps(result,ensure_ascii=False))
- if __name__=='__main__':run()
|