'use strict';
const fs=require('fs'),path=require('path'),A=require('node:assert/strict'),{Worker}=require('node:worker_threads'),{EventEmitter}=require('node:events');
const R=path.resolve(__dirname,'..'),E=path.join(R,'доказательства/редакция-4.11.0');
const K=require(path.join(R,'ресурсы/сценарии/доказательный-контур.js')),Executor=require(path.join(R,'ресурсы/сценарии/исполнитель-контура.js'));
const read=n=>JSON.parse(fs.readFileSync(path.join(R,n))),raw=n=>fs.readFileSync(path.join(R,n));
K.configureSchemas(Object.fromEntries(Object.entries(read('данные/профиль-схем-4.11.0.json').файлы).map(([k,n])=>[k,read(n.slice(1))])));
const workerPath=path.join(R,'ресурсы/сценарии/поток-доказательного-контура.js'),make=()=>new Executor({factory:()=>new Worker(workerPath)}),rows=[];
async function test(name,fn){try{await fn();rows.push({риск:name,результат:'ПРОЙДЕНО'});}catch(e){rows.push({риск:name,результат:'ПРОВАЛЕНО',ошибка:e.stack});}console.log(rows.at(-1).результат+' '+name);}
(async()=>{
 await test('Настоящий поток: канонические байты совпадают с единственным ядром',async()=>{for(const x of [{10:'десять',2:'два',a:'ё\r\n😀'},[null,-0,1e-7,'A\u0308']])A.equal(await make().run('canonical',x),K.canonical(x));});
 await test('Снимок до возврата из postMessage; изменения не попадают в результат',async()=>{const x={name:'исходное'},promise=make().run('canonical',x);x.name='подмена';A.equal(await promise,'{"name":"исходное"}');});
 await test('Отменённый сигнал отвергается до запуска потока',async()=>{const c=new AbortController();c.abort();let launches=0;await A.rejects(()=>new Executor({factory:()=>{launches++;}}).run('seal',{}, {signal:c.signal}),{name:'AbortError'});A.equal(launches,0);});
 await test('Изменение формы после await не принимает результат',async()=>{let current=true;const ex=make(),p=ex.run('canonical',{a:1},{isCurrent:()=>current,onStage:()=>{current=false;}});await A.rejects(()=>p,/изменились/);});
 await test('Двойная операция не заменяет текущую; повтор после успеха возможен',async()=>{const ex=make(),p=ex.run('canonical',{});await A.rejects(()=>ex.run('canonical',{}),/Другая/);A.equal(await p,'{}');A.equal(await ex.run('canonical',[]),'[]');});
 await test('Отмена во время синхронной работы и повтор; исходный буфер не отсоединён',async()=>{const previous=Buffer.from('прежняя работа'),copy=Buffer.from(previous),ex=make(),c=new AbortController();await A.rejects(()=>ex.run('canonical','X'.repeat(4*1024*1024),{signal:c.signal,onStage:s=>{if(s==='схемы загружены')c.abort();}}),{name:'AbortError'});A.deepEqual(previous,copy);A.equal(await ex.run('canonical',{}),'{}');});
 await test('Предел времени завершает поток, а не внешнюю операцию',async()=>{const ex=new Executor({factory:()=>new Worker(workerPath),timeout:1});await A.rejects(()=>ex.run('canonical',{}),/время/);});
 class Fake extends EventEmitter{terminate(){this.terminated=true;}postMessage(m){queueMicrotask(()=>{this.emit('message',{id:m.id-1,protocol:K.WORKER_PROTOCOL,release:K.RELEASE,command:m.command,seq:1,type:'result',value:'null'});this.emit('message',{id:m.id,protocol:K.WORKER_PROTOCOL,release:K.RELEASE,command:m.command,seq:1,type:'result',value:'{}'});});}}
 await test('Искусственный транспорт: устаревший номер не принимается',async()=>{const w=new Fake(),ex=new Executor({factory:()=>w});ex.serial=2;A.equal(await ex.run('canonical',{}),'{}');A(w.terminated);});
 await test('Искусственный транспорт: поздний результат после отмены не принимается',async()=>{const c=new AbortController();class F extends Fake{postMessage(m){c.abort();super.postMessage(m);}}await A.rejects(()=>new Executor({factory:()=>new F()}).run('seal',{}, {signal:c.signal}),{name:'AbortError'});});
 for(const type of ['error','exit'])await test('Искусственный транспорт: сбой '+type+' сохраняет рабочую версию',async()=>{class F extends Fake{postMessage(){queueMicrotask(()=>this.emit(type,type==='error'?Error('Испытательный отказ памяти; не физический OOM'):137));}}const before={kept:true};await A.rejects(()=>new Executor({factory:()=>new F()}).run('canonical',before));A.deepEqual(before,{kept:true});});
 await test('Неподдерживаемая команда получает явную ошибку настоящего потока',async()=>A.rejects(()=>make().run('eval',{code:'0'}),/Неизвестная/));
 const size=K.INTERACTIVE_PROFILE.fileBytes;
 await test('Профиль: 25 файлов принимаются, 26 отклоняются до чтения',()=>{K.resourceCheck(Array(25).fill(new Blob([])),{files:true});A.throws(()=>K.resourceCheck(Array(26).fill(new Blob([])),{files:true}));});
 await test('Профиль: 2 МиБ ровно и превышение на байт',()=>{K.resourceCheck([new Blob([new Uint8Array(size)])],{files:true});A.throws(()=>K.resourceCheck([new Blob([new Uint8Array(size+1)])],{files:true}));});
 await test('Профиль: 8 МиБ ровно и превышение на байт',()=>{const a=Array(4).fill(new Blob([new Uint8Array(size)]));K.resourceCheck(a,{files:true});A.throws(()=>K.resourceCheck([...a,new Blob(['X'])],{files:true}));});
 await test('Профиль: 32 МиБ оболочки ровно и превышение на байт',()=>{K.resourceCheck([new Blob([new Uint8Array(32*1024*1024)])],{files:true,envelope:true});A.throws(()=>K.resourceCheck([new Blob([new Uint8Array(32*1024*1024+1)])],{files:true,envelope:true}));});
 await test('Профиль: строки на точной границе и превышение',()=>{K.resourceCheck('X'.repeat(K.INTERACTIVE_PROFILE.stringUnits));A.throws(()=>K.resourceCheck('X'.repeat(K.INTERACTIVE_PROFILE.stringUnits+1)));});
 await test('Профиль: цикл, общая память и вычисляемые поля запрещены',()=>{const x={};x.self=x;A.throws(()=>K.resourceCheck(x));A.throws(()=>K.resourceCheck(new SharedArrayBuffer(1)));let getter=0;A.throws(()=>K.resourceCheck({get buffer(){getter++;return null;}}));A.equal(getter,0);});
 for(const [key,lim]of [['документы',100],['события_исправления',512],['получатели',64]])await test('Профиль предметной границы '+key,()=>{K.resourceCheck({[key]:Array(lim).fill(null)});A.throws(()=>K.resourceCheck({[key]:Array(lim+1).fill(null)}));});
 await test('Лексическая защита: глубина, повторные ключи, усечение, Unicode',()=>{for(const text of ['{"a":1,"a":2}', '['.repeat(42)+'0'+']'.repeat(42),'{"a":', '{"a":"\\ud800"}','[01]','[1e999]'])A.throws(()=>K.parse(text));A.deepEqual(K.parse('\ufeff {"ё":"😀"}\n'),{ё:'😀'});});
 const inputPaths=read('данные/примеры-продольной-серии/индекс.json').файлы;
 const data={files:inputPaths.slice(0,2).map(p=>new Blob([raw(p)])),names:inputPaths.slice(0,2).map(p=>path.basename(p)),sources:[],decisions:[],revocations:[],migrations:[],restore:false};let panel;
 await test('Настоящий поток: приём и слияние сохраняют исходные байты',async()=>{panel=await make().run('panel-import',data);A.equal(panel.источники.length,2);for(let i=0;i<2;i++){const found=panel.источники.find(s=>s.имя===path.basename(inputPaths[i]));A(found);A(raw(inputPaths[i]).equals(Buffer.from(K.unbase64(found.база64))));}});
 await test('Настоящий поток: круговой перенос панели без роста повторов',async()=>{const sealed=await make().run('seal',panel),back=await make().run('panel-import',{...data,files:[new Blob([K.canonical(sealed)])],names:['панель.json'],sources:panel.источники,restore:true});A.equal(back.источники.length,2);A.equal(K.canonical(back.серии),K.canonical(panel.серии));});
 await test('Передача владения: сериализованные байты совпадают с записью; прежняя панель сохранена',async()=>{const original=K.canonical(panel),out=await make().run('panel-transfer',panel);A(out.bytes instanceof ArrayBuffer);A.equal(Buffer.from(out.bytes).toString(),K.canonical(out.record));A.equal(K.canonical(panel),original);});
 await test('Отмена перед сериализацией не передаёт выходной буфер',async()=>{const c=new AbortController();let hit=false;await A.rejects(()=>make().run('panel-transfer',panel,{signal:c.signal,onStage:s=>{if(s==='перед сериализацией'){hit=true;c.abort();}}}),{name:'AbortError'});A(hit);});
 await test('Настоящий поток: прежняя папка переносится, точные байты сохраняются',async()=>{const bytes=raw('доказательства/редакция-4.7.0/искусственное-дело.json');const c=await make().run('case-import',{file:new Blob([bytes])},{envelope:true});A.equal(c.редакция_схемы,K.RELEASE);A(Buffer.from(await K.recoverOriginal(c.переносы[0])).equals(bytes));const out=await make().run('case-export',c);const again=await make().run('case-import',{file:new Blob([K.canonical(out)])},{envelope:true});A.equal(K.canonical(await K.exportCase(again)),K.canonical(out));});
 const f=read('доказательства/редакция-4.7.0/искусственная-квитанция-470.json');
 const receiptData={receipt:new Blob([JSON.stringify(f.вход)]),request:new Blob([K.unbase64(f.запрос_Base64)]),response:new Blob([K.unbase64(f.ответ_Base64)]),pin:'',pinSource:''};
 await test('Квитанция прежней редакции: native подпись, точные байты, полномочия неизвестны',async()=>{const r=await make().run('receipt',receiptData);A(r.результат.подпись_проверена);A.equal(r.результат.полномочия,'не установлены');A.equal(r.счётчик_повторов,'не изменён');A(!K.canonical(r).includes(f.запрос_Base64));});
 for(const stage of ['снимок принят','схемы загружены','квитанция прочитана','снимок квитанции','ключ квитанции','подпись квитанции','байты квитанции','результат вычислен'])await test('Отмена квитанции на этапе: '+stage,async()=>{let hit=false;const c=new AbortController();await A.rejects(()=>make().run('receipt',receiptData,{signal:c.signal,onStage:s=>{if(s===stage){hit=true;c.abort();}}}),{name:'AbortError'});A(hit);});
 await test('Неверная подпись/байты не заменяют предыдущую квитанцию',async()=>{await A.rejects(()=>make().run('receipt',{...receiptData,response:new Blob(['подмена'])}),/байт/);});

 await test('Изолированный vm: адрес потока привязан к редакции; не проверка браузерного кэша',async()=>{
   const vm=require('node:vm');let used;
   class FakeBrowserWorker{constructor(url){used=new URL(url);}terminate(){}postMessage(m){queueMicrotask(()=>this.onmessage({data:{id:m.id,protocol:K.WORKER_PROTOCOL,release:K.RELEASE,command:m.command,seq:1,type:'result',value:'{}'}}));}}
   const context={ВойнаМетодика:K,URL,DOMException,structuredClone,setTimeout,clearTimeout,Worker:FakeBrowserWorker,document:{baseURI:'https://xn--80advqd.com/переносимый-аудит.html'}};
   vm.createContext(context);vm.runInContext(raw('ресурсы/сценарии/исполнитель-контура.js').toString(),context);
   await new context.ВойнаИсполнитель().run('canonical',{});A.equal(used.searchParams.get('редакция'),K.RELEASE);A.equal(used.origin,'https://xn--80advqd.com');
 });
 await test('Изолированный vm: importScripts содержит редакцию, чужое ядро отклоняется',()=>{
   const vm=require('node:vm');let imported;
   const context={ВойнаМетодика:K,importScripts:url=>{imported=url;},postMessage(){},URL};vm.createContext(context);
   vm.runInContext(raw('ресурсы/сценарии/поток-доказательного-контура.js').toString(),context);
   A.equal(new URL(imported,'https://xn--80advqd.com/ресурсы/сценарии/').searchParams.get('редакция'),K.RELEASE);
   context.ВойнаМетодика={RELEASE:'чужая редакция'};
   A.throws(()=>vm.runInContext(raw('ресурсы/сценарии/поток-доказательного-контура.js').toString(),context),/Редакция ядра/);
 });
 const report={редакция:K.RELEASE,дата:new Date().toISOString(),реализация:process.version,класс:'ИС; настоящие worker_threads и Web Crypto Node.js, отдельно указаны транспортные подмены',команда:'node инструменты/испытать-поток-4-11.cjs',код_SHA256:await K.hash(raw('ресурсы/сценарии/доказательный-контур.js')),сценарий_SHA256:await K.hash(fs.readFileSync(__filename)),исполнитель_SHA256:await K.hash(raw('ресурсы/сценарии/исполнитель-контура.js')),диспетчер_SHA256:await K.hash(raw('ресурсы/сценарии/поток-доказательного-контура.js')),всего:rows.length,пройдено:rows.filter(r=>r.результат==='ПРОЙДЕНО').length,проверки:rows,предел:'Не браузерный допуск, не внешний вызов, не физический OOM. Отменённый локальный поток не означает прекращение внешнего действия.'};fs.writeFileSync(path.join(E,process.argv[2]||'рабочий-поток.json'),JSON.stringify(report,null,2)+'\n');process.exitCode=report.пройдено===report.всего?0:1;
})().catch(e=>{console.error(e);process.exitCode=2;});
