#!/usr/bin/env node
'use strict';
const fs=require('node:fs'),path=require('node:path'),A=require('node:assert/strict'),{Worker}=require('node:worker_threads');
const R=path.resolve(__dirname,'..'),E=path.join(R,'доказательства/редакция-4.9.0'),K=require(path.join(R,'ресурсы/сценарии/доказательный-контур.js')),Executor=require(path.join(R,'ресурсы/сценарии/исполнитель-контура.js')),read=n=>JSON.parse(fs.readFileSync(path.join(R,n))),rows=[];
K.configureSchemas(Object.fromEntries(Object.entries(read('данные/профиль-схем-4.9.0.json').файлы).map(([k,n])=>[k,read(n.slice(1))])));
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()=>{
 let c=await K.importCaseBytes(fs.readFileSync(path.join(E,'ИС-длинное-дело-4-9.json')),'ИС-длинное-дело-4-9.json');const initial=await K.hashCanonical(c),base=Date.parse('2026-09-03T00:00:00.000Z'),added='ИС-Возвращаемый адресат';
 const reasons=(case_,at,goal)=>K.correctionExplanation(case_,new Date(at).toISOString()).исправления[0].остатки.find(x=>x.цель===goal)?.связанные_события.flatMap(e=>e.причины)||[];
 await test('Возвращённый адресат: старое уведомление объяснено прежним составом',()=>A(reasons(c,base+11,'уведомление:'+added).some(r=>r.код==='прежний состав')));
 await test('Повторное распространение: у старого исправления указанное ухудшение',()=>A(reasons(c,base+4,'производная:'+added).some(r=>r.код==='более позднее ухудшение')));
 await test('Причины не включают поздние опоры на всех 30 временных границах',()=>{for(let i=0;i<30;i++){const at=base+i*10+1,ex=K.correctionExplanation(c,new Date(at).toISOString()),visible=new Set(c.события_исправления.filter(e=>Date.parse(e.внесено)<=at&&Date.parse(e.момент_события)<=at).map(e=>e.идентификатор));for(const event of ex.исправления.flatMap(x=>x.остатки).flatMap(x=>x.связанные_события)){A(visible.has(event.идентификатор));for(const reason of event.причины)for(const ref of reason.опоры)A(visible.has(ref));}}});
 await test('Вычисление объяснения не изменяет прежнее дело',async()=>A.equal(await K.hashCanonical(c),initial));
 let serial=0;const t=Date.parse('2026-09-04T00:00:00.000Z'),event=(action,target,delta,extra={})=>({идентификатор:'ИС-ПРИЧИНА-'+(++serial),исправление:c.исправления[0].идентификатор,документ:c.документы[0].идентификатор,действие:action,цель:target,момент_события:new Date(t+delta).toISOString(),внесено:new Date(t+delta).toISOString(),источник:'ИС: искусственная проверка причин',проверяющий:'Искусственное имя',предел_удостоверения:K.EVENT_BOUNDARY,отменяет:null,...extra});
 const first=c.события_исправления.find(e=>e.действие==='исправление источника').идентификатор;
 c=await K.appendCorrectionEvent(c,event('отзыв исправления','источник',0,{отменяет:first}));c=await K.appendCorrectionEvent(c,event('отзыв исправления','источник',1,{отменяет:first}));
 await test('Два отзыва одного события сохраняются в причине',()=>{const r=reasons(c,t+1,'источник').find(x=>x.код==='отозвано');A.equal(r.опоры.length,2);});
 const recipient=c.исправления[0].получатели[0].название;
 c=await K.appendCorrectionEvent(c,event('изменение производной',recipient,2));c=await K.appendCorrectionEvent(c,event('повторное распространение',recipient,2,{внесено:new Date(t+3).toISOString()}));
 await test('Один момент, разное внесение: конфликт виден только после второй записи',()=>{A(!K.correctionTimeline(c,new Date(t+2).toISOString())[0].открытые_остатки.includes('производная:'+recipient));A(reasons(c,t+3,'производная:'+recipient).some(x=>x.код==='противоречие одного момента'));});
 const scope=areas=>({получатели:[recipient,added],области_восстановления:areas,все_получатели_перечислены:true,основание_отсутствия_получателей:'',основание:'ИС: исключение и возвращение области'});
 c=await K.appendCorrectionEvent(c,event('изменение состава исправления','состав',4,{состав:scope(['положение','видимость','репутация'])}));c=await K.appendCorrectionEvent(c,event('изменение состава исправления','состав',5,{состав:scope(['доступ','положение','видимость','репутация'])}));
 await test('Возвращённая область доступа: раннее восстановление не становится действующим',()=>A(reasons(c,t+5,'восстановление:доступ').some(x=>x.код==='прежний состав')));
 await test('Коды причин согласованы с русским словарём',()=>{const dictionary=new Set(read('данные/русский-процессуальный-словарь.json').группы['причины состояния события'].map(x=>x[0]));for(const at of [base+4,base+11,t+1,t+3,t+5])for(const r of K.correctionExplanation(c,new Date(at).toISOString()).исправления.flatMap(x=>x.остатки).flatMap(x=>x.связанные_события).flatMap(x=>x.причины))A(dictionary.has(r.код),r.код);});
 const ex=await K.seal({...K.correctionExplanation(c,new Date(t+5).toISOString()),отпечаток_папки:await K.hashCanonical(K.payload(c))});
 fs.writeFileSync(path.join(E,'ИС-причины-дело.json'),JSON.stringify(await K.exportCase(c),null,2)+'\n');fs.writeFileSync(path.join(E,'ИС-причины-объяснение.json'),JSON.stringify(ex,null,2)+'\n');
 const origin=read('доказательства/редакция-4.9.0/искусственная-цепочка-4.json').производная?.перенос;
 if(origin){await test('Настоящий Node Worker проверяет четыре звена и восстанавливает каждую пару байтов',async()=>{const make=()=>new Executor({factory:()=>new Worker(path.join(R,'ресурсы/сценарии/поток-доказательного-контура.js'))});const history=await make().run('registry-history',origin,{envelope:true});A.equal(history.length,4);for(let i=0;i<4;i++){const link=await make().run('registry-link',{migration:origin,index:i},{envelope:true});A.deepEqual(link,await K.recoverRegistryLink(origin,i));}});}
 const report={редакция:K.RELEASE,дата:new Date().toISOString(),команда:'node инструменты/испытать-причины-4-9.cjs',ядро_SHA256:await K.hash(fs.readFileSync(path.join(R,'ресурсы/сценарии/доказательный-контур.js'))),сценарий_SHA256:await K.hash(fs.readFileSync(__filename)),входы:['ИС-длинное-дело-4-9.json','ИС-причины-дело.json','искусственная-цепочка-4.json'],всего:rows.length,пройдено:rows.filter(r=>r.результат==='ПРОЙДЕНО').length,проверки:rows,предел:'Искусственные входы. Не все последовательности состава, сроков и ответов. Скрытые поздние записи не выводятся в ранний отчёт; объяснение позднего внесения как отдельного внешнего свидетельства не реализовано. Node Worker не является браузером.'};fs.writeFileSync(path.join(E,'причины-остатков.json'),JSON.stringify(report,null,2)+'\n');process.exitCode=report.пройдено===report.всего?0:1;
})().catch(e=>{console.error(e);process.exitCode=2;});
