#!/usr/bin/env node
'use strict';
const fs=require('node:fs'),path=require('node:path'),A=require('node:assert/strict'),{EventEmitter}=require('node:events'),{Worker}=require('node:worker_threads');
const R=path.resolve(__dirname,'..'),target=process.env.CORE_ROOT||R,E=path.join(R,'доказательства/редакция-4.11.0'),K=require(path.join(target,'ресурсы/сценарии/доказательный-контур.js')),Executor=require(path.join(target,'ресурсы/сценарии/исполнитель-контура.js')),rows=[];
async function test(name,fn){try{await fn();rows.push({риск:name,результат:'ПРОЙДЕНО'});}catch(e){rows.push({риск:name,результат:'ПРОВАЛЕНО',ошибка:e.stack});}}
const response=m=>({id:m.id,protocol:K.WORKER_PROTOCOL||'война/поток/2',release:K.RELEASE,command:m.command,seq:1,type:'result',value:'{}'});
class Fake extends EventEmitter{constructor(fn){super();this.fn=fn;}terminate(){this.terminated=true;}postMessage(m){queueMicrotask(()=>this.fn(m,this));}}
const emit=fn=>new Executor({factory:()=>new Fake(fn),timeout:1000});
(async()=>{
 const mutations=[
 ['нет протокола',m=>{delete m.protocol;}],['иная редакция',m=>{m.release='0.0.0';}],['иная команда',m=>{m.command='seal';}],['переставлен номер сообщения',m=>{m.seq=2;}],['лишнее поле',m=>{m.extra=true;}],['нечисловой номер',m=>{m.id='1';}],['будущий номер операции',m=>{m.id+=1;}],['неверный тип результата',m=>{m.value={};}],['некорректный канонический JSON',m=>{m.value='НЕ JSON';}],['неизвестный вид сообщения',m=>{m.type='unknown';}],['массив вместо сообщения',()=>[]],['пустое сообщение',()=>null]
 ];
 for(const[name,mutate]of mutations)await test('Повреждённый ответ: '+name,async()=>{const old={сохранить:'прежняя работа'};const ex=emit((m,w)=>{let r=response(m);const replacement=mutate(r);if(replacement!==undefined)r=replacement;w.emit('message',r);});await A.rejects(()=>ex.run('canonical',old));A.deepEqual(old,{сохранить:'прежняя работа'});});
 await test('Повтор номера сообщения не принимается',async()=>A.rejects(()=>emit((m,w)=>{const stage={...response(m),type:'stage',stage:'начало'};delete stage.value;w.emit('message',stage);w.emit('message',response(m));}).run('canonical',{})));
 await test('Ошибка чтения сообщения отклоняет операцию',async()=>A.rejects(()=>emit((m,w)=>w.emit('messageerror',Error('ИС'))).run('canonical',{})));
 await test('Достоверный конверт и старый номер не заменяют текущий результат',async()=>{const ex=emit((m,w)=>{w.emit('message',{...response(m),id:m.id-1});w.emit('message',response(m));});ex.serial=2;A.equal(await ex.run('canonical',{}),'{}');});
 await test('Поздний результат после отмены не принимается; повтор возможен',async()=>{const control=new AbortController(),ex=emit((m,w)=>{control.abort();w.emit('message',response(m));});await A.rejects(()=>ex.run('canonical',{}, {signal:control.signal}),{name:'AbortError'});A.equal(await emit((m,w)=>w.emit('message',response(m))).run('canonical',{}),'{}');});
 if(!process.env.CORE_ROOT){
  await test('Два экземпляра не запускают одновременно две операции страницы',async()=>{let release;const a=emit((m,w)=>{release=()=>w.emit('message',response(m));}),b=emit((m,w)=>w.emit('message',response(m)));const p=a.run('canonical',{});await A.rejects(()=>b.run('canonical',{}),/Другая/);release();A.equal(await p,'{}');A.equal(await b.run('canonical',{}),'{}');});
  for(const mutation of [m=>m.release='иная',m=>m.protocol='война/поток/1',m=>m.extra=true,m=>m.id=0,m=>m.command='eval'])await test('Настоящий поток отклоняет повреждённый запрос',async()=>{const w=new Worker(path.join(R,'ресурсы/сценарии/поток-доказательного-контура.js'));const m={id:1,command:'canonical',data:{},protocol:K.WORKER_PROTOCOL,release:K.RELEASE};mutation(m);const response=await new Promise((resolve,reject)=>{const timer=setTimeout(()=>reject(Error('таймаут')),5000);w.on('message',r=>{clearTimeout(timer);resolve(r);});w.on('error',reject);w.postMessage(m);});A.equal(response.type,'error');await w.terminate();});
  await test('Настоящий одноразовый поток: повтор ID во время digest прерывает результат',async()=>{const w=new Worker(path.join(R,'ресурсы/сценарии/поток-доказательного-контура.js')),messages=[];try{await new Promise((resolve,reject)=>{const timer=setTimeout(()=>reject(Error('таймаут')),5000);w.on('error',e=>{clearTimeout(timer);reject(e);});w.on('message',m=>{messages.push(m);if(m.type==='error'){setTimeout(()=>{clearTimeout(timer);resolve();},30);}if(m.type==='result'){clearTimeout(timer);reject(Error('Неожиданный результат вместо отказа'));}});const request={id:1,command:'seal',data:{текст:'а'.repeat(1024*1024)},protocol:K.WORKER_PROTOCOL,release:K.RELEASE};w.postMessage(request);w.postMessage({...request,data:{}});});A.equal(messages.filter(x=>x.type==='result').length,0);A.equal(messages.filter(x=>x.type==='error').length,1);}finally{await w.terminate();}});

 }
 const report={редакция:K.RELEASE,дата:new Date().toISOString(),команда:'node инструменты/испытать-протокол-4-11.cjs',ядро_SHA256:await K.hash(fs.readFileSync(path.join(target,'ресурсы/сценарии/доказательный-контур.js'))),исполнитель_SHA256:await K.hash(fs.readFileSync(path.join(target,'ресурсы/сценарии/исполнитель-контура.js'))),сценарий_SHA256:await K.hash(fs.readFileSync(__filename)),реализация:process.version,всего:rows.length,пройдено:rows.filter(r=>r.результат==='ПРОЙДЕНО').length,входы:mutations.map(x=>x[0]),проверки:rows,предел:'Транспортные подмены отделены от настоящего Node Worker. Проверяются структура и порядок, а не доказательство честности произвольного вредоносного кода потока. Межвкладочное ограничение не установлено.'};fs.writeFileSync(path.join(E,process.env.CORE_ROOT?'протокол-основание-4-8.json':'протокол-4-11.json'),JSON.stringify(report,null,2)+'\n');console.log(report.пройдено+'/'+report.всего);process.exitCode=report.пройдено===report.всего?0:1;
})().catch(e=>{console.error(e);process.exitCode=2;});
