#!/usr/bin/env python3
"""Создаёт Worker из точного единственного ядра, схем и диспетчера; закрепляет ресурсы HTML."""
from pathlib import Path
import json,hashlib,base64,re,os
from urllib.parse import urlsplit,unquote
R=Path(__file__).resolve().parent.parent; D=R/'ресурсы/сценарии'; V='4.13.0-wip'
def read(n):return json.loads((R/n).read_text())
def sha(b):return hashlib.sha256(b).hexdigest()
def sri(b):return 'sha256-'+base64.b64encode(hashlib.sha256(b).digest()).decode()
profile=read('данные/профиль-схем-4.13.0.json');schemas={k:read(n.lstrip('/')) for k,n in profile['файлы'].items()};assessment=read('данные/схема-оценки-когнитивной-свободы-4.13.0.json')
core=(D/'доказательный-контур.js').read_text();tail=(R/'инструменты/диспетчер-потока-4-13.js.txt').read_text()
header='/* Генерируемый Worker война.com '+V+'. Не редактировать вручную. Ядро SHA-256: '+sha(core.encode())+' */\n'
boot="""\n(function(){'use strict';
 const node=typeof process!=='undefined'&&!!process.versions?.node;
 const K=globalThis.ВойнаМетодика;let send,receive;
 if(node){const {parentPort}=require('node:worker_threads');send=(m,t=[])=>parentPort.postMessage(m,t);receive=fn=>parentPort.on('message',fn);}
 else{send=(m,t=[])=>postMessage(m,t);receive=fn=>{onmessage=e=>fn(e.data);};}
 const load=async()=>{K.configureSchemas(SCHEMAS);return ASSESSMENT;};
""".replace('SCHEMAS',json.dumps(schemas,ensure_ascii=False,separators=(',',':'))).replace('ASSESSMENT',json.dumps(assessment,ensure_ascii=False,separators=(',',':')))
(D/'поток-доказательного-контура.js').write_text(header+core+boot+tail)
# Only fixed public data, not user inputs, private records or self-referential build manifests.
paths=set([*R.glob('*.json'),*(R/'данные').rglob('*.json'),*(R/'сущности').rglob('*.json'),R/'uai/манифест-модулей.json',D/'поток-доказательного-контура.js'])
paths={p for p in paths if p.name not in ['МАНИФЕСТ-ВЫПУСКА.json']}
entries={ '/'+p.relative_to(R).as_posix():{'bytes':p.stat().st_size,'sha256':sha(p.read_bytes()),'sri':sri(p.read_bytes())} for p in sorted(paths)}
loader=(R/'инструменты/шаблон-загрузчика-4-13.js.txt').read_text().replace('__INTEGRITY_ENTRIES__',json.dumps(entries,ensure_ascii=False,separators=(',',':')))
(D/'целостность-загрузки.js').write_text(loader)
# Operate on tag attributes only, preserving historic main content exactly.
changed=[]
for p in R.rglob('*.html'):
 t=p.read_text();relative=os.path.relpath(D/'целостность-загрузки.js',p.parent)
 if 'целостность-загрузки.js' not in t:
  pos=t.index('<script') if '<script' in t else t.index('</head>')
  t=t[:pos]+'<script defer src="'+relative+'"></script>\n'+t[pos:]
 def tag(m):
  text=m.group(0);k='src' if text.startswith('<script') else 'href'
  if k=='href' and not re.search(r'rel=["\']stylesheet["\']',text):return text
  a=re.search(r'\b'+k+r'=["\']([^"\']+)["\']',text)
  if not a:return text
  u=urlsplit(a.group(1))
  if u.scheme or u.netloc:raise ValueError('Внешний исполняемый ресурс: '+a.group(1))
  f=(R/unquote(u.path).lstrip('/') if u.path.startswith('/') else p.parent/unquote(u.path)).resolve()
  if not f.is_relative_to(R) or not f.is_file():raise ValueError('Нет ресурса: '+str(f))
  text=re.sub(r'\s+(?:integrity|crossorigin)=["\'][^"\']*["\']','',text)
  return text[:-1]+' integrity="'+sri(f.read_bytes())+'" crossorigin="anonymous">'
 t=re.sub(r'<(?:script|link)\b[^>]*>',tag,t);p.write_text(t);changed.append(p.relative_to(R).as_posix())
E=R/'доказательства/редакция-4.13.0';E.mkdir(parents=True,exist_ok=True)
report={'редакция':V,'ядро_SHA256':sha(core.encode()),'worker_SHA256':sha((D/'поток-доказательного-контура.js').read_bytes()),'загрузчик_SHA256':sha(loader.encode()),'данных':len(entries),'страниц':len(changed),'граница':'Генерация и локальные байты. Native SRI/CSP требуют настоящего браузера. Корневой HTML должен быть доставлен доверенным способом; подмена всего HTML не предотвращается SRI.'}
(E/'согласованный-набор.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');print(json.dumps(report,ensure_ascii=False))
