(() => {
  'use strict';
  const $ = (селектор, корень = document) => корень.querySelector(селектор);
  const $$ = (селектор, корень = document) => [...корень.querySelectorAll(селектор)];
  const редакция = '4.9.0-wip';
  const Я = globalThis.ВойнаМетодика;
  const исполнитель = () => new globalThis.ВойнаИсполнитель();
  let правкаФормы=0;
  document.addEventListener('input',()=>{правкаФормы++;},true);
  document.addEventListener('change',()=>{правкаФормы++;},true);
  document.addEventListener('submit',()=>{правкаФормы++;},true);
  document.addEventListener('click',e=>{if(e.target.closest?.('button'))правкаФормы++;},true);
  const задача = async (command,data,options={}) => {
    const revision=правкаФормы,current=()=>revision===правкаФормы&&(options.isCurrent?.()??true);
    const value=await исполнитель().run(command,data,{...options,isCurrent:current});
    if(options.signal?.aborted||!current())throw Error('Данные изменились после расчёта; результат не принят.');
    return value;
  };
  const узелСтатуса = () => {let node=document.querySelector('.методика-статус');if(!node){node=document.createElement('p');node.className='методика-статус';node.setAttribute('role','status');node.setAttribute('aria-live','polite');(document.querySelector('main')||document.body).prepend(node);}return node;};
  const сообщитьОшибку = e => { const node=узелСтатуса();node.textContent='Операция не завершена: '+String(e?.message||e);node.dataset.state='ошибка'; };
  window.addEventListener('unhandledrejection',e=>сообщитьОшибку(e.reason));
  const сейчас = () => new Date().toISOString();
  const сегодня = () => new Date().toISOString().slice(0, 10);
  const безопасно = значение => String(значение ?? '').replace(/[&<>'"]/g, знак => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[знак]));
  const прочитать = (ключ, запасное) => { try { const значение = localStorage.getItem(ключ); return значение ? Я.parse(значение,40*1024*1024) : запасное; } catch { const node=document.querySelector('.методика-статус'); if(node){node.dataset.storage='ошибка';node.textContent='Локальное хранилище недоступно или повреждено. Данные не считаются восстановленными; сохраните новые записи в файл.';} return запасное; } };
  const записать = (ключ, значение) => { try { localStorage.setItem(ключ, JSON.stringify(значение)); return true; } catch { const узел = узелСтатуса(); узел.textContent = 'Не удалось сохранить работу в браузере. Сохраните файл перед закрытием страницы.'; return false; } };
  const получить = async путь => {
    const ответ = await fetch(путь, {cache: 'no-store'});
    if (!ответ.ok) throw new Error(`Не удалось получить ${путь}: ${ответ.status}`);
    return ответ.json();
  };
  const скачать = (имя, содержимое, тип = 'application/json;charset=utf-8') => {
    const ссылка = document.createElement('a');
    ссылка.href = URL.createObjectURL(new Blob([содержимое], {type: тип}));
    ссылка.download = имя;
    document.body.append(ссылка);
    ссылка.click();
    setTimeout(() => { URL.revokeObjectURL(ссылка.href); ссылка.remove(); }, 1000);
  };
  const отпечаток = объект => Я.hash(Я.canonical(Я.payload(объект)));
  const данныеФормы = форма => {
    const результат = {};
    for (const элемент of форма.elements) {
      if (!элемент.name || элемент.disabled) continue;
      if (элемент.type === 'checkbox') результат[элемент.name] = элемент.checked;
      else if (элемент.type === 'radio') { if (элемент.checked) результат[элемент.name] = элемент.value; }
      else результат[элемент.name] = элемент.value;
    }
    return результат;
  };
  const установитьСегодня = узел => { if (узел && !узел.value) узел.value = сегодня(); };


  function запуститьКвитанцию(){
    const form=$('#форма-квитанции');if(!form)return;
    const status=$('#статус-квитанции'),result=$('#результат-квитанции'),save=$('#экспорт-квитанции'),cancel=$('#отменить-квитанцию');
    let accepted=null,busy=false,controller=null;
    form.addEventListener('submit',async e=>{
      e.preventDefault();if(busy){status.textContent='Повторное действие не принято: проверка ещё выполняется.';return;}
      busy=true;controller=new AbortController();cancel.disabled=false;
      try{
        const receipt=form.elements.квитанция.files[0],request=form.elements.запрос.files[0],response=form.elements.ответ.files[0];
        if(!receipt||!request||!response)throw Error('Нужны три исходных файла.');
        Я.resourceCheck([receipt,request,response],{files:true});
        const pin=form.elements.ключ.value.trim().toLowerCase(),pinSource=form.elements.публикация.value.trim();
        if(pin&&!/^[a-f0-9]{64}$/.test(pin))throw Error('Отпечаток ключа должен содержать ровно 64 шестнадцатеричных знака.');
        if(pin&&!pinSource)throw Error('Укажите заявленный источник отдельно полученного отпечатка. Его подлинность программа не устанавливает.');
        const out=await задача('receipt',{receipt,request,response,pin,pinSource},{signal:controller.signal,onStage:stage=>{status.textContent='Проверка квитанции: '+stage+'.';}});
        accepted=out;result.textContent=JSON.stringify(out,null,2);save.disabled=false;
        status.textContent='Подпись и точные байты сопоставлены. Публикация, полномочия, отзыв ключа и реальность вызова не установлены. Число повторов не изменено.';
      }catch(error){status.textContent='Проверка не завершена. '+error.message+' Предыдущий результат не заменён.';}
      finally{busy=false;cancel.disabled=true;controller=null;}
    });
    cancel.addEventListener('click',()=>controller?.abort());
    save.addEventListener('click',()=>{if(!accepted)return;скачать('проверка-квитанции-4.9.0-wip.json',Я.canonical(accepted)+'\n');status.textContent='Подготовлена передача результата без исходных байтов запроса и ответа. Квитанция и отпечатки могут содержать чувствительные сведения; проверьте файл перед публикацией. Сохранение на диске не установлено.';});
    $('#очистить-квитанцию').addEventListener('click',()=>{if(busy){status.textContent='Сначала отмените текущую проверку.';return;}accepted=null;form.reset();result.textContent='Принятых квитанций нет.';save.disabled=true;status.textContent='Текущий результат очищен; исходные файлы не изменены.';form.elements.квитанция.focus();});
  }

  function показатьПричины(out){
    const host=$('#объяснение-причины');if(!host)return;host.replaceChildren();
    const rows=out.исправления.flatMap(c=>c.остатки.map(r=>({...r,исправление:c.исправление}))),summary=document.createElement('p');
    summary.textContent=rows.length?'Открытых остатков: '+rows.length+'. Ниже показаны первые '+Math.min(rows.length,12)+'. Полный перечень, отпечатки и опоры сохранены в JSON.':'Открытых остатков в выбранном срезе нет. Это полнота заявленных событий, не удостоверение внешнего исполнения.';host.append(summary);
    for(const row of rows.slice(0,12)){
      const card=document.createElement('article');card.className='карточка-причины';const title=document.createElement('h3');title.textContent=row.цель;card.append(title);
      const description=document.createElement('p');description.textContent=row.причина;card.append(description);
      for(const event of row.связанные_события.slice(0,8)){
        const item=document.createElement('p'),label=document.createElement('strong');label.textContent=event.идентификатор+' · '+event.действие;item.append(label,document.createElement('br'));
        item.append(document.createTextNode((event.причины||[]).map(r=>r.код+': '+r.пояснение+(r.опоры.length?' Опоры: '+r.опоры.join(', '):'')).join(' ')+' Документ: '+event.документ+'.'));card.append(item);
      }
      if(row.связанные_события.length>8){const more=document.createElement('p');more.textContent='Остальные '+(row.связанные_события.length-8)+' опор доступны в полном JSON ниже и в экспорте.';card.append(more);}host.append(card);
    }
  }

  // ---------------- Переносимый аудит ----------------
  async function запуститьАудит() {
    if (document.body.dataset.method !== 'аудит') return;
    const статус = $('#статус-аудита');
    const областиУзел = $('#области-аудита');
    const праваУзел = $('#права-аудита');
    const ключ = 'война-аудит-когнитивной-свободы-4-9';
    let схема;
    let работа = прочитать(ключ, {области: {}, права: {}});
    const состояния = ['не проверено','подтверждено','частично','провалено','не применимо'];
    const классы = ['ДФ','НП','НО','АВ','ЗТ','ТВ','ИС','ПР'];
    try {
      схема = await получить('данные/схема-оценки-когнитивной-свободы-4.9.0.json');
    } catch (ошибка) {
      статус.textContent = `Схема не загружена. ${ошибка.message}`;
      статус.dataset.state = 'ошибка';
      return;
    }
    $('#аудит-идентификатор').value = crypto.randomUUID();
    let журналы = [];
    const снятьСерию = () => {
      if (!$('#серия-включена').checked) return null;
      return {идентификатор:$('#серия-код').value.trim(), наблюдатель:$('#серия-наблюдатель').value.trim(), протокол:$('#серия-протокол').value.trim(), окно:$('#серия-окно').value.trim(), начало:$('#серия-начало').value, конец:$('#серия-конец').value,
        условие:$('#серия-условие').value.trim(), условия_серии:$('#серия-условия').value.split('\n').map(x=>x.trim()).filter(Boolean),
        изменяемые_переменные:$('#серия-переменная').value.split('\n').map(x=>x.trim()).filter(Boolean),
        неизменяемые_условия:Я.parse($('#серия-постоянные').value), наблюдения:Я.parse($('#серия-наблюдения').value), независимость:{состояние:$('#наблюдатель-независимость').value,группа_управления:$('#наблюдатель-группа').value.trim(),основание:$('#наблюдатель-основание').value.trim(),проверяющий:$('#наблюдатель-проверяющий').value.trim(),идентификатор_группы:$('#наблюдатель-группа-код').value.trim()||null,история_основания:Я.parse($('#наблюдатель-история').value||'[]')}};
    };
    const заполнитьСерию = с => {
      $('#серия-включена').checked = !!с;
      for(const [id,key] of [['независимость','состояние'],['группа','группа_управления'],['основание','основание'],['проверяющий','проверяющий']])$('#наблюдатель-'+id).value=с?.независимость?.[key]||(key==='состояние'?'не проверено':'');
      $('#наблюдатель-группа-код').value=с?.независимость?.идентификатор_группы||'';$('#наблюдатель-история').value=JSON.stringify(с?.независимость?.история_основания||[],null,2);
      if (!с) return;
      for (const [id,key] of [['код','идентификатор'],['наблюдатель','наблюдатель'],['протокол','протокол'],['окно','окно'],['начало','начало'],['конец','конец'],['условие','условие']]) $('#серия-'+id).value=с[key];
      $('#серия-условия').value=с.условия_серии.join('\n'); $('#серия-переменная').value=с.изменяемые_переменные.join('\n');
      $('#серия-постоянные').value=JSON.stringify(с.неизменяемые_условия,null,2); $('#серия-наблюдения').value=JSON.stringify(с.наблюдения,null,2);
    };
    $('#импорт-журнала').addEventListener('change', async e => {
      const file=e.target.files[0]; if (!file) return;
      try { if (file.size>Я.LIMIT) throw Error('Журнал превышает 5 МиБ.'); const bytes=await file.arrayBuffer(); const rows=Я.parse(new TextDecoder('utf-8',{fatal:true}).decode(bytes)); if (!Array.isArray(rows)) throw Error('Журнал должен содержать массив наблюдений.');
        журналы=[{имя:file.name,байты:file.size,ша256:await Я.hash(bytes)}]; $('#серия-наблюдения').value=JSON.stringify(rows,null,2);
        $('#отпечаток-журнала').textContent=`SHA-256 исходных байтов журнала: ${журналы[0].ша256}. После редактирования наблюдений исходный журнал остаётся отдельной опорой.`;
      } catch(error) { $('#отпечаток-журнала').textContent=error.message; } finally { e.target.value=''; }
    });
    await запуститьПанель(схема);
    запуститьКвитанцию();
    установитьСегодня($('#аудит-дата'));
    const вариантыСостояния = состояния.map(с => `<option value="${с}">${с[0].toUpperCase()+с.slice(1)}</option>`).join('');
    const вариантыКласса = схема.восемь_классов_доказательств.map(к => `<option value="${к.код}">${к.код} · ${безопасно(к.класс)}</option>`).join('');
    const карта = (запись, тип) => {
      const сохранённое = (работа[тип] || {})[запись.код] || {};
      const критический = запись.критический_рубеж ? '<span class="критический-знак">КРИТИЧЕСКИЙ</span>' : '';
      const требование = запись.требование || запись.минимальное_доказательство || '';
      const провал = запись.сигнал_провала || '';
      return `<article class="методика-карта" data-code="${запись.код}" data-type="${тип}" data-search="${безопасно((запись.код+' '+(запись.область||запись.право)+' '+требование+' '+провал).toLowerCase())}">
        <header><span class="номер">${запись.код}</span>${критический}<h3>${безопасно(запись.область || запись.право)}</h3></header>
        <p><strong>Требование.</strong> ${безопасно(требование)}</p>
        <p><strong>Минимальное доказательство.</strong> ${безопасно(запись.минимальное_доказательство || запись.требование)}</p>
        ${провал ? `<p><strong>Сигнал провала.</strong> ${безопасно(провал)}</p>` : ''}
        <div class="поле-сетка два"><label>Состояние<select data-field="состояние">${вариантыСостояния}</select></label><label>Класс доказательства<select data-field="класс"><option value="">Не указан</option>${вариантыКласса}</select></label></div>
        <label>Доказательная запись<textarea data-field="доказательство" rows="4" placeholder="Факт, дата, версия, источник, повтор, отпечаток и предел вывода.">${безопасно(сохранённое.доказательство || '')}</textarea></label>
      </article>`;
    };
    областиУзел.innerHTML = схема.восемнадцать_областей.map(з => карта(з,'области')).join('');
    праваУзел.innerHTML = схема.двадцать_прав.map(з => карта(з,'права')).join('');
    $$('.методика-карта').forEach(карточка => {
      const тип = карточка.dataset.type;
      const код = карточка.dataset.code;
      const сохранённое = (работа[тип] || {})[код] || {};
      $('[data-field="состояние"]', карточка).value = сохранённое.состояние || 'не проверено';
      $('[data-field="класс"]', карточка).value = сохранённое.класс || '';
      карточка.addEventListener('input', () => {
        работа[тип] ||= {};
        работа[тип][код] = {
          состояние: $('[data-field="состояние"]', карточка).value,
          класс: $('[data-field="класс"]', карточка).value,
          доказательство: $('[data-field="доказательство"]', карточка).value,
          обновлено: сейчас()
        };
        записать(ключ, работа);
      });
    });
    const фильтр = (поле, селектор) => поле?.addEventListener('input', событие => {
      const запрос = событие.target.value.trim().toLowerCase();
      $$(селектор).forEach(карта => карта.hidden = !!запрос && !карта.dataset.search.includes(запрос));
    });
    фильтр($('#поиск-областей'), '#области-аудита .методика-карта');
    фильтр($('#поиск-прав-42'), '#права-аудита .методика-карта');

    const снять = тип => $$(тип === 'области' ? '#области-аудита .методика-карта' : '#права-аудита .методика-карта').map(карта => ({
      код: карта.dataset.code,
      состояние: $('[data-field="состояние"]', карта).value,
      класс_доказательства: $('[data-field="класс"]', карта).value,
      доказательство: $('[data-field="доказательство"]', карта).value.trim()
    }));
    const рассчитать = () => {
      const области = снять('области');
      const права = снять('права');
      const р = Я.score({области, права}, схема);
      $('#итог-аудита').innerHTML = `<strong>${р.итог}</strong><p>Полнота записи: ${р.полнота} из 100. Не проверено: ${р.неизвестно}. Без доказательной опоры: ${р.без_опоры}. Неподтверждённых заявлений об исполнении: ${р.неподтверждённые_заявления}. Критические провалы: ${р.критические_провалы.join(', ') || 'нет'}.</p><p>${безопасно(р.граница)}</p>`;
      $('#итог-аудита').dataset.verdict = р.итог.toLowerCase().replaceAll(' ', '-');
      return {области, права, критические_провалы:р.критические_провалы, расчётная_полнота:р.полнота, итог:р.итог};
    };
    $('[data-action="рассчитать-аудит"]')?.addEventListener('click', рассчитать);
    $('[data-action="экспорт-аудита"]')?.addEventListener('click', async () => {
      const расчёт = рассчитать();
      const запись = {
        редакция_схемы: редакция,
        идентификатор: $('#аудит-идентификатор').value.trim(),
        дата_создания: сейчас(),
        проверяемая_система: $('#аудит-система').value.trim(),
        версия_системы: $('#аудит-версия').value.trim(),
        условия: $('#аудит-условия').value.trim(),
        дата_проверки: $('#аудит-дата').value,
        области: расчёт.области,
        права: расчёт.права,
        доказательства: расчёт.области.concat(расчёт.права).filter(з => з.доказательство),
        критические_провалы: расчёт.критические_провалы,
        итог: расчёт.итог,
        расчётная_полнота: расчёт.расчётная_полнота,
        граница: 'Контрольный отпечаток выявляет изменение записи, но не доказывает истинность внесённых фактов.'
      };
      try {
      запись.серия = снятьСерию();
      запись.журналы = журналы;
      запись.ша256 = await отпечаток(запись);
      const ошибки = Я.auditErrors(запись, схема);
      if (ошибки.length) throw Error(ошибки.join('\n'));
      скачать(`аудит-когнитивной-свободы-${сегодня()}.json`, JSON.stringify(запись, null, 2));
      статус.textContent = `Запись сформирована. SHA-256: ${запись.ша256}`;
      статус.dataset.state = 'готово';
      } catch (ошибка) { статус.textContent = ошибка.message; статус.dataset.state = 'ошибка'; }
    });
    $('#импорт-аудита')?.addEventListener('change', async событие => {
      const файл = событие.target.files?.[0]; if (!файл) return;
      try {
        const источник = await Я.ingest(await файл.arrayBuffer(), файл.name, схема);
        if (источник.ошибки.length) throw Error(источник.ошибки.join('\n'));
        const запись = источник.запись;
        const ожидаемый = источник.отпечаток_исходного_файла;
        $('#аудит-идентификатор').value = запись.идентификатор || `ИМП-${ожидаемый.slice(0,16)}`;
        заполнитьСерию(запись.серия);
        журналы = запись.журналы || [];
        $('#аудит-система').value = запись.проверяемая_система || '';
        $('#аудит-версия').value = запись.версия_системы || '';
        $('#аудит-условия').value = запись.условия || '';
        $('#аудит-дата').value = запись.дата_проверки || сегодня();
        for (const тип of ['области','права']) for (const з of запись[тип] || []) {
          const карта = $(`.методика-карта[data-type="${тип}"][data-code="${з.код}"]`);
          if (!карта) continue;
          $('[data-field="состояние"]', карта).value = з.состояние;
          $('[data-field="класс"]', карта).value = з.класс_доказательства || '';
          $('[data-field="доказательство"]', карта).value = з.доказательство || '';
          работа[тип][з.код] = {состояние:з.состояние,класс:з.класс_доказательства || '',доказательство:з.доказательство || '',обновлено:сейчас()};
        }
        записать(ключ, работа); рассчитать();
        статус.textContent = `Запись принята. Контрольный отпечаток совпал: ${ожидаемый}`;
        статус.dataset.state = 'готово';
      } catch (ошибка) {
        статус.textContent = `Запись отклонена: ${ошибка.message}`;
        статус.dataset.state = 'ошибка';
      } finally { событие.target.value = ''; }
    });
    $('[data-action="сброс-аудита"]')?.addEventListener('click', () => {
      if (!confirm('Удалить локальную работу по аудиту?')) return;
      try{localStorage.removeItem(ключ);location.reload();}catch(e){сообщитьОшибку(Error('Очистка хранилища не выполнена. Прежняя работа сохранена в памяти вкладки.'));}
    });
    if(статус.dataset.storage!=='ошибка')статус.textContent = 'Схема загружена. Локальное сохранение проверяется при изменениях; экспортируйте работу перед закрытием.';
    статус.dataset.state = 'готово';
    рассчитать();
  }

  // ---------------- Симметричные пары ----------------
  async function запуститьСимметрию() {
    if (document.body.dataset.method !== 'симметрия') return;
    const данные = await получить('данные/набор-симметричных-запросов-4.9.0.json');
    const область = $('#симметрия-область');
    const пара = $('#симметрия-пара');
    const форма = $('#форма-симметрии');
    const ключ = 'война-журнал-симметрии-4-8';
    let журнал = прочитать(ключ, []);
    const области = [...new Set(данные.пары.map(з => з.область))];
    область.insertAdjacentHTML('beforeend', области.map(з => `<option>${безопасно(з)}</option>`).join(''));
    const доступные = () => данные.пары.filter(з => !область.value || з.область === область.value);
    const заполнитьСписок = () => {
      const текущий = пара.value;
      пара.innerHTML = доступные().map(з => `<option value="${з.код}">${з.код} · ${безопасно(з.тема)}</option>`).join('');
      if ([...пара.options].some(o => o.value === текущий)) пара.value = текущий;
      показатьПару();
    };
    const показатьПару = () => {
      const з = данные.пары.find(п => п.код === пара.value) || доступные()[0];
      if (!з) return;
      форма.elements.код.value = з.код;
      $('#симметрия-запросы').innerHTML = `<article><span>ЗАПРОС А</span><p>${безопасно(з.запрос_а)}</p></article><article><span>ЗАПРОС Б</span><p>${безопасно(з.запрос_б)}</p></article><aside><strong>Измерять:</strong> ${безопасно(з.измерять.join(', '))}.<br><strong>Условия:</strong> ${безопасно(з.неизменяемые_условия.join(', '))}.</aside>`;
    };
    const показатьЖурнал = () => {
      $('#журнал-симметрии').innerHTML = журнал.length ? журнал.slice().reverse().map(з => `<article class="журнал-запись"><h3>${безопасно(з.код)} · ${безопасно(з.тема)}</h3><p>${безопасно(з.система)} · ${безопасно(з.версия)} · ${безопасно(з.дата)}</p><p>Повторы: ${безопасно(з.повторы_а)} / ${безопасно(з.повторы_б)}. Различие: ${безопасно(з.различие)}</p><p>Предел: ${безопасно(з.предел)}</p></article>`).join('') : '<p>Локальных наблюдений пока нет.</p>';
    };
    $('#каталог-симметрии').innerHTML = данные.пары.map(з => `<article class="методика-карта"><header><span class="номер">${з.код}</span><h3>${безопасно(з.тема)}</h3></header><p><strong>А.</strong> ${безопасно(з.запрос_а)}</p><p><strong>Б.</strong> ${безопасно(з.запрос_б)}</p></article>`).join('');
    область.addEventListener('change', заполнитьСписок);
    пара.addEventListener('change', показатьПару);
    форма.addEventListener('submit', событие => {
      событие.preventDefault();
      const д = данныеФормы(форма);
      const исходная = данные.пары.find(з => з.код === д.код);
      журнал.push({...д, тема: исходная?.тема || '', область: исходная?.область || '', система: $('#симметрия-система').value.trim(), версия: $('#симметрия-версия').value.trim(), дата: сейчас()});
      записать(ключ, журнал); показатьЖурнал();
      форма.elements.ответ_а.value=''; форма.elements.ответ_б.value=''; форма.elements.различие.value='';
    });
    $('[data-action="экспорт-симметрии"]')?.addEventListener('click', async () => {
      const запись={редакция,создано:сейчас(),система:$('#симметрия-система').value.trim(),версия:$('#симметрия-версия').value.trim(),набор:'данные/набор-симметричных-запросов-4.9.0.json',наблюдения:журнал,граница:'Различие не доказывает причинность без повторов и контроля условий.'};
      запись.ша256=await отпечаток(запись); скачать(`журнал-симметрии-${сегодня()}.json`,JSON.stringify(запись,null,2));
    });
    $('[data-action="сброс-симметрии"]')?.addEventListener('click',()=>{if(confirm('Удалить локальный журнал симметрии?')){if(!записать(ключ,[])){сообщитьОшибку('Очистка не выполнена: хранилище недоступно; рабочий журнал сохранён.');return;}журнал=[];показатьЖурнал();}});
    заполнитьСписок(); показатьЖурнал();
  }

  // ---------------- Чрезмерные отказы ----------------
  async function запуститьОтказы() {
    if (document.body.dataset.method !== 'отказы') return;
    const данные=await получить('данные/набор-проверки-чрезмерных-отказов-4.9.0.json');
    const список=$('#отказы-пара'); const форма=$('#форма-отказов'); const ключ='война-журнал-отказов-4-8'; let журнал=прочитать(ключ,[]);
    список.innerHTML=данные.пары.map(з=>`<option value="${з.код}">${з.код} · ${безопасно(з.тема)}</option>`).join('');
    установитьСегодня($('#отказы-дата'));
    const показатьПару=()=>{const з=данные.пары.find(п=>п.код===список.value);if(!з)return;форма.elements.код.value=з.код;$('#отказы-запросы').innerHTML=`<article><span>ИССЛЕДОВАТЕЛЬСКИЙ ЗАПРОС</span><p>${безопасно(з.исследовательский_запрос)}</p></article><article class="граница"><span>ЗАПРОС НА КОНКРЕТНОЕ ВРЕДНОЕ ИСПОЛНЕНИЕ</span><p>${безопасно(з.запрос_на_конкретное_вредное_исполнение)}</p></article><aside>${безопасно(з.ожидаемое_различие)}</aside>`;};
    const показатьЖурнал=()=>{$('#журнал-отказов').innerHTML=журнал.length?журнал.slice().reverse().map(з=>`<article class="журнал-запись"><h3>${безопасно(з.код)} · ${безопасно(з.тема)}</h3><p>${безопасно(з.система)} · ${безопасно(з.версия)} · ${безопасно(з.дата)}</p><p>${безопасно(з.вывод)}</p><p>Исследование содержательно: ${з.полезный_исследовательский_ответ?'да':'нет'} · граница различена: ${з.различена_граница?'да':'нет'} · без операционных деталей: ${з.нет_операционных_деталей?'да':'нет'}</p></article>`).join(''):'<p>Локальных испытаний пока нет.</p>';};
    список.addEventListener('change',показатьПару);
    форма.addEventListener('submit',событие=>{событие.preventDefault();const д=данныеФормы(форма);const исходная=данные.пары.find(з=>з.код===д.код);журнал.push({...д,тема:исходная?.тема||'',система:$('#отказы-система').value.trim(),версия:$('#отказы-версия').value.trim(),дата:$('#отказы-дата').value||сегодня(),создано:сейчас()});записать(ключ,журнал);показатьЖурнал();форма.elements.ответ_исследование.value='';форма.elements.ответ_исполнение.value='';форма.elements.вывод.value='';});
    $('[data-action="экспорт-отказов"]')?.addEventListener('click',async()=>{const запись={редакция,создано:сейчас(),набор:'данные/набор-проверки-чрезмерных-отказов-4.9.0.json',наблюдения:журнал,граница:'Набор не содержит ответов, операционных параметров или инструкций.'};запись.ша256=await отпечаток(запись);скачать(`журнал-чрезмерных-отказов-${сегодня()}.json`,JSON.stringify(запись,null,2));});
    $('[data-action="сброс-отказов"]')?.addEventListener('click',()=>{if(confirm('Удалить локальный журнал отказов?')){if(!записать(ключ,[])){сообщитьОшибку('Очистка не выполнена: хранилище недоступно; рабочий журнал сохранён.');return;}журнал=[];показатьЖурнал();}});
    показатьПару();показатьЖурнал();
  }

  // ---------------- Пять документов обжалования ----------------
  // Дело обжалования ведётся тем же ядром, что и переносимый аудит.
  // ---------------- Реестр требований ----------------
  // ---------------- Жизненный цикл ----------------
  async function запуститьЖизненныйЦикл() {
    if (document.body.dataset.method !== 'жизненный-цикл') return;
    const данные=await получить('данные/карта-жизненного-цикла-данных-4.9.0.json');
    const объекты=[...данные.книги,...данные.исследовательские_серии];
    const показать=фильтр=>{$('#карта-жизненного-цикла').innerHTML=объекты.filter(з=>фильтр==='все'||з.тип===фильтр).map(з=>`<article class="цикл-объект" data-type="${безопасно(з.тип)}"><header><span>${безопасно(з.код)}</span><h2>${безопасно(з.объект)}</h2><p>${безопасно(з.описание||з.тип)}</p></header><ol>${з.этапы.map((э,номер)=>`<li><span class="этап-номер">${String(номер+1).padStart(2,'0')}</span><div><h3>${безопасно(э.этап)}</h3><p>${безопасно(э.состояние)}</p><p><strong>Доказательство:</strong> ${безопасно(э.доказательство)}</p></div></li>`).join('')}</ol></article>`).join('');};
    $$('[data-filter-life]').forEach(кнопка=>кнопка.addEventListener('click',()=>{$$('[data-filter-life]').forEach(к=>к.classList.add('вторичная'));кнопка.classList.remove('вторичная');показать(кнопка.dataset.filterLife);}));
    показать('все');
  }

  // ---------------- Граф ----------------
  async function запуститьГраф() {
    if (document.body.dataset.method !== 'граф') return;
    const статус=$('#вердикт-графа');
    try {
      const д=await получить('данные/проверка-полноты-графа-4.9.0.json');
      $('#цифры-графа').innerHTML=`<span><strong>${д.число_узлов}</strong> узлов</span><span><strong>${д.число_связей}</strong> связей</span><span><strong>${д.число_публичных_страниц}</strong> страниц</span>`;
      $('#сводка-графа').innerHTML=`<div class="сводка-ячейка"><strong>${д.страницы_без_узла.length}</strong><span>страниц без узла</span></div><div class="сводка-ячейка"><strong>${д.узлы_с_отсутствующим_объектом.length}</strong><span>узлов без объекта</span></div><div class="сводка-ячейка"><strong>${д.изолированные_узлы.length}</strong><span>изолированных узлов</span></div><div class="сводка-ячейка"><strong>${д.новые_материалы_без_памяти_или_происхождения.length}</strong><span>материалов без связи</span></div>`;
      const список=(селектор,массив)=>{$(селектор).innerHTML=массив.length?`<ul>${массив.map(з=>`<li>${безопасно(typeof з==='string'?з:JSON.stringify(з))}</li>`).join('')}</ul>`:'<p class="пройдено">Нарушений не обнаружено.</p>';};
      список('#страницы-без-узла',д.страницы_без_узла); список('#узлы-без-объекта',д.узлы_с_отсутствующим_объектом); список('#изолированные-узлы',д.изолированные_узлы);
      статус.textContent=`${д.итог}. Проверка выполнена ${д.дата}. ${д.граница}`; статус.dataset.state=д.итог==='ПРОЙДЕНО'?'готово':'ошибка';
    } catch(ошибка){статус.textContent=`Отчёт не загружен: ${ошибка.message}`;статус.dataset.state='ошибка';}
  }


  // ---------------- Объединение и продольная панель ----------------
  async function запуститьПанель(схема) {
    const статус=$('#статус-панели'); let источники=[];let разрешения=[];let отзывы=[];let переносы=[];let панель=Я.merge([],схема);let занято=false;let отменаПриёма=null;
    $('#отменить-приём-панели').addEventListener('click',()=>{отменаПриёма?.abort();статус.textContent='Запрошена отмена. Прежняя панель остаётся рабочей.';});
    const печатныйДокумент=(название,содержимое)=>`<!doctype html><html lang="ru"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src 'none'"><title>${безопасно(название)} — война.com</title><style>body{font:16px/1.5 Georgia,serif;max-width:72rem;margin:2rem auto;padding:0 1rem;color:#111;background:#fff}h1,h2,h3{break-after:avoid}article,section{margin:1rem 0}table{border-collapse:collapse;width:100%;table-layout:fixed}th,td{border:1px solid;padding:.5rem;text-align:left;overflow-wrap:anywhere}pre{white-space:pre-wrap;overflow-wrap:anywhere}code{overflow-wrap:anywhere}nav,button,input{display:none}@page{size:A4;margin:16mm}@media print{body{margin:0;padding:0;font-size:10pt}.лист{break-before:page}}</style><body><h1>${безопасно(название)}</h1><p>война.com · 4.9.0-wip · производная, сформированная локально. Контрольные отпечатки подтверждают тождество байтов, не истинность.</p>${содержимое}</body></html>`;
    const представить=(готовая=null)=>{
      панель=готовая||Я.merge(источники,схема,разрешения,отзывы,переносы);
      $('#источники-панели').innerHTML=панель.источники.length?панель.источники.map((s,i)=>`<article class="журнал-запись"><h3>${безопасно(s.имя)}</h3><p>${s.ошибки.length?'ОТЛОЖЕНА: ошибки структуры или целостности':'ПРИНЯТА ДЛЯ СОПОСТАВЛЕНИЯ'} · ${s.байты} байт</p><p>Отпечаток исходных байтов: <code>${s.отпечаток_исходного_файла}</code></p><p>Отпечаток исходной записи: <code>${s.отпечаток_исходной_записи||'не установлен'}</code></p>${s.ошибки.concat(s.предупреждения).map(x=>`<p>${безопасно(x)}</p>`).join('')}<div class="контур-действия"><button type="button" data-source-download="${i}">Сохранить исходник</button><button class="вторичная" type="button" data-source-remove="${i}">Исключить из панели</button></div></article>`).join(''):'<p>Исходные записи ещё не загружены. Пустая панель не является серией.</p>';
      $('#конфликты-панели').innerHTML=панель.конфликты.length?панель.конфликты.map(c=>`<article class="журнал-запись"><h3>${безопасно(c.вид)}</h3><p>${безопасно(c.сообщение)}</p><p>${c.исключает_из_сравнения?'Сравнение заблокировано.':'Различие сохранено как предупреждение.'}</p><details><summary>Исходные записи конфликта</summary>${c.источники.map(h=>`<p><code>${h}</code></p>`).join('')}</details></article>`).join(''):'<p>Структурных конфликтов не выявлено. Это не подтверждение истинности.</p>';
      $('#окна-панели').innerHTML=панель.серии.length?панель.серии.map(s=>`<section class="серия-панели"><h3>${безопасно(s.идентификатор)} · ${s.состояние}</h3><p>Заявленные наблюдатели: ${безопасно(s.наблюдатели.join(', '))}. Их независимость автоматически не подтверждается.</p><div class="продольные-окна">${s.окна.map(w=>w.нет_данных?`<article class="окно-пустое"><h4>${безопасно(w.окно)}</h4><p>Нет данных. Не считается временным окном проверки.</p></article>`:`<article><h4>${безопасно(w.окно)}</h4><p>${w.начало} — ${w.конец}</p><strong>Полнота ${w.полнота}%</strong><p>Состав окна: ${безопасно(w.наблюдатели.join(', '))}.</p>${w.сопоставимость_с_предыдущим?`<p>Сопоставимость по заявленным полям: ${w.сопоставимость_с_предыдущим.сопоставимо_по_заявленным_полям?'совпадают':'ограничена'}. Состав по условиям ${w.сопоставимость_с_предыдущим.состав_изменился?'изменился':'не изменился'}; длительность ${w.сопоставимость_с_предыдущим.длительность_изменилась?'изменилась':'не изменилась'}; заявления о независимости ${w.сопоставимость_с_предыдущим.сведения_о_независимости_изменились?'изменились':'не изменились'}.</p><p>Добавились: ${безопасно(w.сопоставимость_с_предыдущим.добавились.join(', ')||'нет')}. Выбыли: ${безопасно(w.сопоставимость_с_предыдущим.выбыли.join(', ')||'нет')}.</p>`:''}<details><summary>Состав, основания независимости и полнота по наблюдателям</summary><pre>${безопасно(JSON.stringify(w.независимость_наблюдателей,null,2))}</pre></details><p>Изменение: ${w.изменение_полноты===null?'исходная точка':`${w.изменение_полноты>0?'+':''}${w.изменение_полноты} п. п.`}</p><p>${w.состояния.join(' / ')}</p><p>${w.изменение_состояния}</p><details><summary>Изменения по кодам: ${w.изменения_по_кодам.length}</summary>${w.изменения_по_кодам.map(c=>`<p>${безопасно(c.условие)} · ${безопасно(c.код)}: ${безопасно(c.было.join(' / ')||'нет данных')} → ${безопасно(c.стало.join(' / ')||'нет данных')}</p>`).join('')||'<p>Изменений между соседними окнами не записано.</p>'}</details><p>${Object.entries(w.повторы).map(([k,v])=>`${безопасно(k)}: ${v} повторов`).join('; ')}</p><p>Повторно внесённых наблюдений: ${w.дубли_наблюдений}. Ненаблюдательных записей: ${w.ненаблюдательные_записи}.</p><details><summary>Состояния прав и областей по условиям</summary>${Object.entries(w.аудит_по_условиям).map(([c,items])=>`<h5>${безопасно(c)}</h5>${items.map(a=>`<p>Источник: <code>${a.источник}</code></p>${[...a.области,...a.права].map(r=>`<p>${безопасно(r.код)} · ${безопасно(r.состояние)} · ${безопасно(r.класс_доказательства||'без класса')}</p>`).join('')}`).join('')}`).join('')}</details></article>`).join('')}</div>${s.причины.map(r=>`<p>${безопасно(r)}</p>`).join('')}<p>${s.причинность}</p></section>`).join(''):`<div class="продольные-окна">${[1,2,3].map(i=>`<article class="окно-пустое"><h3>Временное окно ${i}</h3><p>Нет данных.</p></article>`).join('')}</div>`;
      const chooser=$('#разрешение-конфликт');const oldChoice=chooser.value;
      chooser.innerHTML=панель.конфликты.map((c,i)=>`<option value="${i}">${безопасно(c.вид)} · ${безопасно(c.сообщение)}</option>`).join('');if(oldChoice)chooser.value=oldChoice;
      $('#журнал-разрешений').innerHTML=разрешения.map(r=>`<article class="журнал-запись"><h3>Решение ${безопасно(r.идентификатор)} · ${безопасно(панель.состояния_решений.find(x=>x.решение===r.идентификатор)?.состояние||'не установлено')}</h3><p>${безопасно(r.проверяющий)} · ${r.дата_время}</p><p>${безопасно(r.основание)}</p><p>SHA-256 производной: <code>${r.ша256}</code></p><p>Первоначально заявлено исключение: ${r.исключённые_из_сравнения.map(безопасно).join(', ')}. Исходники сохранены.</p></article>`).join('')||'<p>Документированных решений пока нет.</p>';
      $('#состояние-проекции').textContent=`В исходном составе конфликтов: ${панель.конфликты.length}; в сравнительной проекции: ${панель.конфликты_проекции.length}. Исключённых исходников: ${панель.исключённые_из_сравнения.length}. Рассмотрение не устанавливает истинность.`;
      $('#статусы-решений').textContent=JSON.stringify(панель.состояния_решений,null,2);
      $('#отзыв-решение').innerHTML=разрешения.map(x=>`<option value="${безопасно(x.идентификатор)}">${безопасно(x.идентификатор)}</option>`).join('');
      $('#происхождение-панели').innerHTML=переносы.map((m,i)=>`<article><h4>Перенос из ${безопасно(m.исходная_редакция)}</h4><p>Исходный файл: <code>${m.отпечаток_файла}</code></p><p>${безопасно(m.граница)}</p><p>Добавлены неизвестные поля: ${безопасно(m.добавленные_неизвестные.join('; '))}.</p><button type="button" data-migration-original="${i}">Сохранить точную прежнюю оболочку</button></article>`).join('')||'<p>Переносов прежних оболочек нет.</p>';
      $$('[data-migration-original]').forEach(b=>b.addEventListener('click',async()=>{try{const m=переносы[Number(b.dataset.migrationOriginal)];скачать(m.исходник.имя,await Я.recoverOriginal(m),'application/octet-stream');статус.textContent='Исходные байты переданы браузеру. Проверьте сохранённый файл.';}catch(e){статус.textContent=e.message;}}));
      $('#без-серии').textContent=`Принятых записей без параметров серии: ${панель.без_серии.length}. Они не засчитываются как повторяемая серия.`;
      $$('[data-source-download]').forEach(b=>b.addEventListener('click',()=>{const s=панель.источники[Number(b.dataset.sourceDownload)];скачать(s.имя,Я.unbase64(s.база64),'application/octet-stream');}));
      $$('[data-source-remove]').forEach(b=>b.addEventListener('click',()=>{if(занято){статус.textContent='Состав не изменён: выполняется другая операция.';return;}const s=панель.источники[Number(b.dataset.sourceRemove)];if(разрешения.some(r=>r.источники.includes(s.отпечаток_исходного_файла))||переносы.some(m=>m.сохранённые_источники.includes(s.отпечаток_исходного_файла))){статус.textContent='Исходник связан с решением конфликта и не может быть удалён отдельно. Сохраните панель; для нового независимого состава очистите её целиком.';return;}источники=источники.filter(x=>x.отпечаток_исходного_файла!==s.отпечаток_исходного_файла);представить();статус.textContent='Запись исключена из текущей панели, не исправлена и не удалена из исходного файла.';}));
    };
    const импорт=async(files,restore=false)=>{
      if(занято)return;занято=true;отменаПриёма=new AbortController();$('#отменить-приём-панели').disabled=false;статус.textContent='Проверяются исходные байты и структура.';
      try {
        Я.resourceCheck(files,{files:true,envelope:restore});
        const original=панель;
        const candidate=await задача('panel-import',{files,names:files.map(f=>f.name),sources:источники,decisions:разрешения,revocations:отзывы,migrations:переносы,restore},{signal:отменаПриёма.signal,envelope:restore,isCurrent:()=>original===панель,onStage:stage=>{статус.textContent='Рабочий поток: '+stage+'. Прежняя панель сохранена.';}});
        источники=candidate.источники;разрешения=candidate.разрешения;отзывы=candidate.отзывы;переносы=candidate.переносы;представить(candidate);
        статус.textContent=`Исходников: ${панель.источники.length}. Конфликтов и предупреждений сопоставления: ${панель.конфликты.length}. Исходные файлы не перезаписаны.`;
      }catch(e){статус.textContent=`Импорт не завершён; прежняя панель сохранена. ${e.message}`;}finally{занято=false;отменаПриёма=null;$('#отменить-приём-панели').disabled=true;}
    };
    $('#форма-разрешения').addEventListener('submit',async e=>{
      e.preventDefault();if(занято){статус.textContent='Другая операция ещё выполняется; повторное действие не принято.';return;}занято=true;
      try{const f=e.currentTarget,input=данныеФормы(f),c=панель.конфликты[Number(input.конфликт)];if(!c)throw Error('Нет выбранного конфликта.');
        const r=await Я.resolveConflict(источники,схема,{редакция_схемы:редакция,вид:'решение конфликта',идентификатор:crypto.randomUUID(),отпечаток_конфликта:await Я.hash(Я.canonical(c)),источники:c.источники,исключённые_из_сравнения:input.исключить.split('\n').map(x=>x.trim()).filter(Boolean),проверяющий:input.проверяющий.trim(),основание:input.основание.trim(),дата_время:сейчас(),действие:'исключение из сравнения без изменения источников'});
        if(Я.canonical(данныеФормы(f))!==Я.canonical(input))throw Error('Форма изменилась во время проверки; решение не внесено.');
        const next=[...разрешения,r];Я.merge(источники,схема,next,отзывы,переносы);разрешения=next;представить();статус.textContent='Решение сохранено как отдельная производная; спорные исходники не удалены и не прибавлены к повторам.';
      }catch(error){статус.textContent='Решение не внесено: '+error.message;}finally{занято=false;}
    });
    $('#форма-отзыва').addEventListener('submit',async e=>{
      e.preventDefault();if(занято)return;занято=true;
      try{const form=e.currentTarget,data=данныеФормы(form),r=разрешения.find(x=>x.идентификатор===data.решение);if(!r)throw Error('Выберите сохранённое решение.');
        const result=await Я.revokeResolution(разрешения,{редакция_схемы:редакция,вид:'отзыв решения конфликта',идентификатор:crypto.randomUUID(),решение:r.идентификатор,отпечаток_решения:r.ша256,дата_время:сейчас(),проверяющий:data.проверяющий,основание:data.основание});
        if(Я.canonical(данныеФормы(form))!==Я.canonical(data))throw Error('Форма изменилась; отзыв не внесён.');
        Я.merge(источники,схема,разрешения,[...отзывы,result],переносы);отзывы.push(result);представить();статус.textContent='Отзыв записан новой производной. Исходное решение и спорные записи сохранены.';
      }catch(error){статус.textContent='Отзыв не внесён: '+error.message;}finally{занято=false;}
    });
    $('#импорт-серий').addEventListener('change',async e=>{await импорт([...e.target.files]);e.target.value='';});
    $('#импорт-панели').addEventListener('change',async e=>{await импорт([...e.target.files],true);e.target.value='';});
    $('[data-action="пример-панели"]').addEventListener('click',async()=>{
      if(источники.length&&!confirm('Демонстрационные записи будут добавлены к текущей панели и явно обозначены как испытательные. Продолжить?'))return;
      try { const список=await получить('данные/примеры-продольной-серии/индекс.json');const files=[];
      for(const path of список.файлы){const response=await fetch(path,{cache:'no-store'});if(!response.ok)throw Error('Пример не загружен.');files.push(new File([await response.arrayBuffer()],path.split('/').at(-1)));}
      await импорт(files);статус.textContent+=' Это искусственный пример, а не реальное наблюдение.';}catch(e){статус.textContent=`Искусственный пример не загружен: ${e.message}`;}
    });
    const передатьПанель=async печать=>{
      if(занято){статус.textContent='Выполняется другая операция; повторное действие не принято.';return;}
      занято=true;отменаПриёма=new AbortController();$('#отменить-приём-панели').disabled=false;
      try{const revision=правкаФормы,original=панель,windows=$('#окна-панели').innerHTML,conflicts=$('#конфликты-панели').innerHTML;
        const {record:out,bytes:передаваемыеБайты}=await задача('panel-transfer',original,{signal:отменаПриёма.signal,isCurrent:()=>original===панель});await Я.checkpoint(отменаПриёма.signal);
        if(revision!==правкаФормы||original!==панель)throw Error('Панель изменилась; прежний экспорт не передан.');
        if(печать){const readable={...out,источники:out.источники.map(({база64,...rest})=>rest),переносы:(out.переносы||[]).map(m=>({...m,исходник:{...m.исходник,база64:undefined}}))};
          скачать('продольная-панель-печать.html',печатныйДокумент('Продольная панель',`<p>Отпечаток производной: <code>${out.ша256}</code></p>${windows}<h2>Конфликты</h2>${conflicts}<pre>${безопасно(JSON.stringify(readable,null,2))}</pre><p>Исходные байты включены в JSON, не в печатную производную.</p>`),'text/html;charset=utf-8');
        }else{скачать('продольная-панель-4.9.0-wip.json',передаваемыеБайты);$('#отпечаток-панели').textContent=`SHA-256 объединённой производной: ${out.ша256}`;}
        статус.textContent='Файл подготовлен и передан браузеру. Сохранение на диске и печатная пагинация не проверены.';
      }catch(error){статус.textContent='Передача не завершена; панель сохранена. '+error.message;}
      finally{занято=false;отменаПриёма=null;$('#отменить-приём-панели').disabled=true;}
    };
    $('[data-action="экспорт-панели"]').addEventListener('click',()=>передатьПанель(false));
    $('[data-action="печать-панели"]').addEventListener('click',()=>передатьПанель(true));
    $('[data-action="очистить-панель"]').addEventListener('click',()=>{if(занято){статус.textContent='Очистка не выполнена во время другой операции.';return;}if(confirm('Убрать все записи из текущей панели? Несохранённая панель будет потеряна.')){источники=[];разрешения=[];отзывы=[];переносы=[];представить();статус.textContent='Панель и её производные очищены в этой вкладке.';}});
    представить();
  }
  // ---------------- Переносная папка дела ----------------
  async function запуститьОбжалование() {
    if(document.body.dataset.method!=='обжалование')return;
    const форма=$('#форма-процессуального-комплекта');const статус=$('#статус-дела');const редактор=$('#редактор-документа');let выбран='';let изменено=false;let редакторИзменён=false;let предыдущаяПапка=null;let делоЗанято=false;
    let дело={редакция_схемы:редакция,вид:'папка дела',идентификатор:crypto.randomUUID(),дата_проверки:сегодня(),происхождение:null,поля:{},документы:[],приложения:[],хронология:[],исправления:[],события_исправления:[]};
    const тексты = д => {
    const шапка=(название,д)=>`${название}\nРедакция формы: 4.9.0\nДата формирования: ${д.момент}\nОрганизация: ${д.организация||'[не указано]'}\nДата решения: ${д.дата_решения||'[не указано]'}\nНомер дела: ${д.номер_дела||'[не указано]'}\nЗаявитель: ${д.заявитель||'[не указано]'}\nКонтакт: ${д.контакт||'[не указано]'}\n\n`;

    return [
      шапка('ДОКУМЕНТ 1. УВЕДОМЛЕНИЕ О СУЩЕСТВЕННОМ МАШИННОМ РЕШЕНИИ',д)+`Оспариваемое решение:\n${д.описание_решения}\n\nМатериальное влияние:\n${д.материальное_влияние}\n\nИзвестная система:\n${д.известная_система||'Неизвестна. Требуется назвать систему, версию, ответственную структуру и точную роль.'}\n\nТРЕБОВАНИЕ\nСообщите, использовались ли алгоритм, оценка, ранжирование, прогнозная модель, генеративная система или автоматизированный рабочий процесс. Назовите систему, версию, дату, ответственную структуру, её точную роль и способ существенного влияния.\n\nСрок ответа: ${д.срок_ответа||'[указать применимый срок]'}`,
      шапка('ДОКУМЕНТ 2. ЖУРНАЛ ДОКАЗАТЕЛЬСТВ',д)+`Сохраните и перечислите все исходные записи, данные, выводы, источники, правила, пороги, версии, журналы и исправляемые поля, повлиявшие на решение.\n\nПредполагаемая ошибка:\n${д.предполагаемая_ошибка||'Требуется раскрытие записи для проверки.'}\n\nДля каждой опоры укажите: источник, дату, версию, происхождение, класс доказательства, применённое правило и возможность исправления. Не заменяйте конкретную опору общей формулой политики.`,
      шапка('ДОКУМЕНТ 3. ЗАПРОС НА ПЕРЕСМОТР ЧЕЛОВЕКОМ',д)+`Прошу передать дело полномочному проверяющему-человеку, способному изменить или отменить исход.\n\nКонтекст для проверки:\n${д.контекст||'[контекст не указан]'}\n\nПроверяющий должен иметь доступ к доказательствам, возможность принять дополнительный контекст, полномочие изменить исход и обязанность изложить конкретные причины. Сообщите роль проверяющего, процедуру, срок и порядок внешнего обжалования.`,
      шапка('ДОКУМЕНТ 4. СООБЩЕНИЕ ОБ ИСПРАВЛЕНИИ И ВОССТАНОВЛЕНИИ',д)+`Ошибочная или спорная запись:\n${д.предполагаемая_ошибка||'[не указано]'}\n\nТребуемое исправление:\n${д.требуемое_исправление}\n\nИсправьте исходную запись у её источника. После исправления пересмотрите решение и восстановите утраченный доступ, доход, положение, видимость, репутацию или иную затронутую возможность. Сообщите дату исправления и ответственную роль.`,
      шапка('ДОКУМЕНТ 5. ТРЕБОВАНИЕ УДАЛЕНИЯ НИЖЕЛЕЖАЩЕЙ МЕТКИ',д)+`Известные нижележащие системы и получатели:\n${д.нижележащие_системы||'Неизвестны. Требуется предоставить карту всех получателей и производных записей.'}\n\nИсправление не завершено, пока ошибочная метка продолжает действовать в производных профилях, системах получателей, оценках, рекомендациях и последующих решениях. Предоставьте карту передачи, подтвердите удаление или исправление каждой копии и укажите порядок повторной проверки.`
    ];

    };
    const статусСмены=сообщение=>{статус.textContent=сообщение;изменено=true;};
    const событие=(описание,документ='')=>дело.хронология.push({дата_время:сейчас(),событие:описание,документ});
    const найти=()=>дело.документы.find(x=>x.идентификатор===выбран);
    const показатьДокумент=()=>{
      const d=найти();редактор.hidden=!d;
      if(!d){$('#результат-комплекта').textContent='Сформируйте документы или загрузите папку дела.';return;}
      for(const k of ['название','вид','дата','срок','основание_срока','состояние','текст'])редактор.elements[k].value=d[k]||'';
      редактор.elements.связанные_документы.value=d.связанные_документы.join('\n');
      редактор.elements.основание_изменения.value='';
      редактор.elements.вопросы.value=JSON.stringify(d.вопросы,null,2);редактор.elements.ответы_на_вопросы.value=JSON.stringify(d.ответы_на_вопросы,null,2);редактор.elements.проверка_срока.value=JSON.stringify(d.проверка_срока,null,2);редактор.elements.точное_время.value=d.точное_время||'';редактор.elements.получение.value=JSON.stringify(d.получение||null,null,2);редакторИзменён=false;
      $('#результат-комплекта').textContent=d.текст;
      $('#история-документа').innerHTML=d.история.map(h=>`<p>${безопасно(h.дата_время)} · ${безопасно(h.состояние)} · ${безопасно(h.основание)}</p>`).join('');
      $('#приложения-документа').textContent=`Связанные приложения: ${d.приложения.join(', ')||'нет'}.`;
    };
    const показать=()=>{
      $('#дело-точное-время').value=дело.точное_время_проверки||'';
      const choices=(id,rows)=>{const el=$(id),old=el.value;el.innerHTML=rows.map(r=>`<option value="${безопасно(r.идентификатор)}">${безопасно(r.название||r.запись||r.идентификатор)}</option>`).join('');if(rows.some(r=>r.идентификатор===old))el.value=old;};
      choices('#событие-исправление',дело.исправления);choices('#событие-документ',дело.документы);
      $('#внести-событие').disabled=!дело.исправления.length||!дело.документы.length;
      $('#итоги-событий').innerHTML=Я.correctionTimeline(дело).map(r=>`<article class="журнал-запись"><h3>${безопасно(r.исправление)} · ${безопасно(r.итог)}</h3><p>Видимых событий: ${r.видимых_событий}. Момент среза: ${r.момент_среза}.</p><p>Открытые остатки: ${безопасно(r.открытые_остатки.join('; ')||'нет по заявленным событиям')}.</p><pre>${безопасно(JSON.stringify({состав_среза:r.состав_среза,неактуальных_событий:r.неактуальных_событий,состояния:r.состояния},null,2))}</pre><p>${безопасно(r.граница)}</p></article>`).join('')||'<p>Проверок исправления пока нет.</p>';
      $('#история-событий').textContent=JSON.stringify(дело.события_исправления||[],null,2);
      $('#дело-код').textContent=дело.идентификатор;$('#дело-дата').value=дело.дата_проверки;
      $('#документы-дела').innerHTML=дело.документы.map(d=>`<option value="${безопасно(d.идентификатор)}">${безопасно(d.идентификатор)} · ${безопасно(d.название)} · ${d.состояние}</option>`).join('');
      if(!найти())выбран=дело.документы[0]?.идентификатор||'';$('#документы-дела').value=выбран;
      const молчание=Я.silence(дело);
      $('#остатки-ответов').innerHTML=Я.responseCoverage(дело).map(r=>`<article class="журнал-запись"><h3>${безопасно(r.документ)} · ${r.полнота}</h3><p>Открытые вопросы: ${безопасно(r.открытые_вопросы.join(', ')||'нет перечисленных')}.</p><p>${r.пояснение}</p><p>${r.применимость_срока}</p><details><summary>Связи ответов с вопросами</summary><pre>${безопасно(JSON.stringify(r.вопросы,null,2))}</pre></details></article>`).join('')||'<p>Обращений пока нет.</p>';
      $('#молчание-дела').innerHTML=молчание.length?молчание.map(r=>`<article class="журнал-запись"><h3>${безопасно(r.документ)}</h3><p>Срок: ${r.срок}; прошло дней: ${r.дней_после_срока}.</p><p>${безопасно(r.основание_срока)}</p><p>${r.состояние}</p></article>`).join(''):'<p>В деле не выявлены отправленные документы с истёкшим сроком без связанного ответа. Это не подтверждение получения ответа вне дела.</p>';
      $('#хронология-дела').innerHTML=дело.хронология.map(h=>`<p><time>${h.дата_время}</time> · ${безопасно(h.событие)} ${безопасно(h.документ)}</p>`).join('')||'<p>Событий пока нет.</p>';
      $('#приложения-дела').innerHTML=дело.приложения.map((a,i)=>`<article class="журнал-запись"><h3>${безопасно(a.имя)}</h3><p>${a.байты} байт · ${a.идентификатор}</p><p>SHA-256: <code>${a.ша256}</code></p><button type="button" data-attachment="${i}">Сохранить исходные байты приложения</button></article>`).join('')||'<p>Приложений пока нет. Исходные файлы включаются в структурированный экспорт без отправки на сервер.</p>';
      $$('[data-attachment]').forEach(b=>b.addEventListener('click',()=>{const a=дело.приложения[Number(b.dataset.attachment)];скачать(a.имя,Я.unbase64(a.база64),'application/octet-stream');}));
      $('#исправления-дела').innerHTML=дело.исправления.map(c=>{const r=Я.correction(c);return `<article class="журнал-запись"><h3>${безопасно(c.запись)} · ${r.состояние}</h3><p>${c.дата} · ${безопасно(c.идентификатор)}</p>${r.причины.map(t=>`<p>${безопасно(t)}</p>`).join('')}<p>${r.граница}</p><details><summary>Запись проверки исправления</summary><pre>${безопасно(JSON.stringify(c,null,2))}</pre></details></article>`;}).join('')||'<p>Исправления не проверены.</p>';
      $('#происхождение-дела').innerHTML=(дело.переносы||[]).map((m,i)=>`<p>Исходная редакция ${безопасно(m.исходная_редакция)} · <code>${m.отпечаток_файла}</code> <button type="button" data-case-original="${i}">Сохранить прежний исходник</button></p>`).join('')||'<p>Прежняя оболочка не переносилась.</p>';
      $$('[data-case-original]').forEach(b=>b.addEventListener('click',async()=>{try{const m=дело.переносы[Number(b.dataset.caseOriginal)];скачать(m.исходник.имя,await Я.recoverOriginal(m),'application/octet-stream');}catch(e){статус.textContent=e.message;}}));
      $('[data-action="вернуть-папку"]').disabled=!предыдущаяПапка;
      показатьДокумент();
    };
    const добавитьДокумент=(название,текст,вид='обращение')=>{
      const id=`ДОК-${String(дело.документы.length+1).padStart(4,'0')}`;const stamp=сейчас();
      const d={идентификатор:id,название,вид,дата:сегодня(),срок:дело.поля.срок_ответа||'',основание_срока:'Срок указан заявителем; применимость требует проверки.',состояние:'черновик',текст,связанные_документы:[],приложения:[],вопросы:[],ответы_на_вопросы:[],проверка_срока:{состояние:'не проверено',источник:'',проверяющий:'',дата:'',основание:''},история:[{дата_время:stamp,состояние:'черновик',основание:'Документ создан локально, не отправлен.'}]};
      d.точное_время=null;d.получение=null;d.срезы=[Я.documentSnapshot(d,stamp)];дело.документы.push(d);событие('Создан документ.',id);выбран=id;return d;
    };
    форма.addEventListener('submit',e=>{
      e.preventDefault();if(дело.документы.length&&!confirm('Добавить ещё пять документов? Существующие документы и их история сохранятся.'))return;
      const fields=данныеФормы(форма);if(fields.срок_ответа && fields.срок_ответа<сегодня()){статус.textContent='Срок ответа раньше даты нового документа. Для исторического документа добавьте отдельную запись и явно задайте её дату и срок.';return;}
      дело.поля=fields;const stamp=сейчас();
      const д={...дело.поля,момент:stamp};тексты(д).forEach((t,i)=>добавитьДокумент(['Уведомление о машинном решении','Журнал доказательств','Пересмотр человеком','Исправление исходной записи','Исправление производных записей'][i],t));
      показать();статусСмены('Пять черновиков добавлены в папку. Они не отправлены адресату.');
    });
    $('#документы-дела').addEventListener('change',e=>{if(редакторИзменён&&!confirm('Отбросить ещё не внесённые поля редактора? История сохранённых документов не изменится.')){e.target.value=выбран;return;}выбран=e.target.value;показатьДокумент();});редактор.addEventListener('input',()=>{редакторИзменён=true;изменено=true;});
    редактор.addEventListener('submit',async e=>{
      e.preventDefault();if(делоЗанято)return;делоЗанято=true;
      try{const prev=найти();if(!prev)throw Error('Нет выбранного документа.');const input=данныеФормы(редактор),expected=Я.canonical(дело),revision=правкаФормы,chosen=выбран;
        const changes=Object.fromEntries(['название','вид','дата','срок','основание_срока','состояние','текст'].map(k=>[k,input[k]]));
        changes.точное_время=input.точное_время.trim()||null;for(const k of ['вопросы','ответы_на_вопросы','проверка_срока','получение'])changes[k]=Я.parse(input[k]);changes.связанные_документы=input.связанные_документы.split('\n').map(x=>x.trim()).filter(Boolean);
        const candidate=await Я.reviseDocument(дело,chosen,changes,input.основание_изменения);
        if(правкаФормы!==revision||Я.canonical(дело)!==expected||chosen!==выбран||Я.canonical(данныеФормы(редактор))!==Я.canonical(input))throw Error('Работа или поля редактора изменились во время проверки; повторите сохранение.');
        дело=candidate;событие('Изменён документ: '+input.основание_изменения,выбран);показать();статусСмены('Изменение, основание и срез содержания внесены в историю.');
      }catch(error){статус.textContent='Изменение не внесено: '+error.message;}finally{делоЗанято=false;}
    });
    $('[data-action="новый-документ"]').addEventListener('click',()=>{добавитьДокумент('Новый документ','','заметка');показать();статусСмены('Добавлен черновик. Укажите вид, дату, содержание и связанные документы.');});
    $('#дело-дата').addEventListener('change',e=>{if(!Я.date(e.target.value)){статус.textContent='Некорректная дата проверки.';return;}дело.дата_проверки=e.target.value;дело.точное_время_проверки=null;событие('Изменена дата проверки сроков: '+e.target.value);показать();статусСмены('Сроки пересчитаны по указанной дате; применимость сроков автоматически не установлена.');});
    $('#импорт-приложений').addEventListener('change',async e=>{
      if(делоЗанято){статус.textContent='Выполняется другая операция; приложения не внесены.';e.target.value='';return;}делоЗанято=true;
      try{
        if(!найти())throw Error('Сначала выберите документ, к которому относится приложение.');
        const expected=правкаФормы;const pending=structuredClone(дело);const d=pending.документы.find(x=>x.идентификатор===выбран);
        for(const file of e.target.files){if(file.size>Я.LIMIT)throw Error(`Приложение ${file.name} превышает 5 МиБ.`);const a=await Я.attachment(await file.arrayBuffer(),file.name,file.type);if(!pending.приложения.some(x=>x.ша256===a.ша256))pending.приложения.push(a);if(!d.приложения.includes(a.идентификатор))d.приложения.push(a.идентификатор);}
        const stamp=сейчас();d.история.push({дата_время:stamp,состояние:d.состояние,основание:'Добавление проверенных приложений по явному выбору пользователя.'});d.срезы??=[];d.срезы.push(Я.documentSnapshot(d,stamp));const errors=await Я.caseErrors(pending);if(errors.length)throw Error(errors.join('\n'));if(правкаФормы!==expected)throw Error('Рабочая папка изменилась во время проверки. Приложения не внесены; повторите импорт.');дело=pending;событие('Приложения сохранены с исходными байтами и отпечатками.',выбран);показать();статусСмены('Приложения включены в папку; серверу ничего не отправлено.');
      }catch(error){статус.textContent=error.message;}finally{делоЗанято=false;e.target.value='';}
    });
    let отменаДела=null;
    $('#отменить-дело').addEventListener('click',()=>{отменаДела?.abort();статус.textContent='Запрошена отмена. Дождитесь сообщения о результате; рабочая папка не заменяется частичным результатом.';});
    const начатьДело=()=>{if(делоЗанято)throw Error('Выполняется другая операция.');делоЗанято=true;отменаДела=new AbortController();$('#отменить-дело').disabled=false;};
    const завершитьДело=()=>{делоЗанято=false;отменаДела=null;$('#отменить-дело').disabled=true;};
    const готовыйЭкспорт=async()=>{
      const expected=правкаФормы,formExpected=Я.canonical(данныеФормы(редактор));
      const out=await задача('case-transfer',дело,{signal:отменаДела?.signal});
      await Я.checkpoint(отменаДела?.signal);
      if(правкаФормы!==expected||Я.canonical(данныеФормы(редактор))!==formExpected)throw Error('Дело или редактор изменились; устаревший файл не передан.');
      return out;
    };
    $('[data-action="сохранить-комплект"]').addEventListener('click',async()=>{
      if(делоЗанято)return;начатьДело();try{const {record:out,bytes}=await готовыйЭкспорт();скачать('папка-дела-4.9.0-wip.json',bytes);статус.textContent=`Папка сформирована. SHA-256 производной: ${out.ша256}. Изменения в редакторе, не внесённые кнопкой «Сохранить изменение», не включаются. Файл передан браузеру; сохранение на диске не проверено.`;}catch(e){статус.textContent=e.message;}finally{завершитьДело();}
    });
    $('#импорт-дела').addEventListener('change',async e=>{
      const file=e.target.files[0];if(!file||делоЗанято)return;начатьДело();
      try{
        const expected=правкаФормы,formExpected=Я.canonical(данныеФормы(редактор));Я.resourceCheck([file],{files:true,envelope:true});const raw=await file.arrayBuffer();
        const restored=await задача('case-import',{file},{signal:отменаДела.signal,envelope:true});
        if(дело.документы.length&&!confirm('Заменить текущую рабочую папку импортируемой? Несохранённые изменения будут потеряны.'))return;
        if(правкаФормы!==expected||Я.canonical(данныеФормы(редактор))!==formExpected)throw Error('Рабочая папка или редактор изменились во время проверки. Импорт не заменил их.');const oldHash=await Я.hash(raw);await Я.checkpoint(отменаДела.signal);if(правкаФормы!==expected||Я.canonical(данныеФормы(редактор))!==formExpected)throw Error('Папка изменилась перед записью; импорт не применён.');предыдущаяПапка=structuredClone(дело);дело=restored;
        for(const [key,value] of Object.entries(дело.поля||{}))if(форма.elements[key])форма.elements[key].value=value;
        выбран='';изменено=false;показать();статус.textContent=`Папка прочитана и пересчитана; прежняя схема при необходимости перенесена с исходными байтами. SHA-256 загруженного файла: ${oldHash}`;
      }catch(error){статус.textContent=`Импорт отклонён; текущая папка сохранена. ${error.message}`;}finally{завершитьДело();e.target.value='';}
    });
    $('[data-action="печать-дела"]').addEventListener('click',async()=>{
      if(делоЗанято)return;начатьДело();try{
        const {record:out}=await готовыйЭкспорт();const readable=structuredClone(out);readable.приложения.forEach(a=>delete a.база64);for(const m of readable.переносы||[])delete m.исходник.база64;
        const html=`<!doctype html><html lang="ru"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'"><title>Папка дела — война.com</title><style>body{font:16px/1.5 Georgia,serif;max-width:70rem;padding:1rem;margin:auto}pre{white-space:pre-wrap;overflow-wrap:anywhere}h1,h2,h3{break-after:avoid}article{margin:2rem 0;border-top:1px solid;padding-top:1rem}code{overflow-wrap:anywhere}@page{size:A4;margin:16mm}@media print{body{font-size:10pt;padding:0}article{break-before:page}}</style><body><h1>Папка дела</h1><p>война.com · 4.9.0-wip · ${безопасно(out.идентификатор)}</p><p>Дата проверки: ${out.дата_проверки}. Отпечаток структурированной папки: <code>${out.ша256}</code>.</p><p>Локальная производная. Не подтверждает отправку, применимость срока или истинность приложений. Байты приложений сохранены отдельно внутри структурированного файла JSON.</p>${out.документы.map(d=>`<article><h2>${безопасно(d.название)}</h2><p>${безопасно(d.идентификатор)} · ${d.состояние} · ${d.дата}</p><p>Срок: ${безопасно(d.срок||'не установлен')}. Основание: ${безопасно(d.основание_срока)}</p><pre>${безопасно(d.текст)}</pre></article>`).join('')}<article><h2>Хронология, состояния и доказательные связи</h2><pre>${безопасно(JSON.stringify(readable,null,2))}</pre></article></body></html>`;
        скачать('папка-дела-печать.html',html,'text/html;charset=utf-8');статус.textContent='Печатное представление сформировано. Откройте файл и используйте печать браузера.';
      }catch(e){статус.textContent=e.message;}finally{завершитьДело();}
    });
    $('#объяснить-остатки').addEventListener('click',async()=>{if(делоЗанято)return;начатьДело();try{const out=await задача('case-explanation',дело,{signal:отменаДела.signal});показатьПричины(out);$('#объяснение-остатков').textContent=JSON.stringify(out,null,2);скачать('объяснение-остатков-4.9.0-wip.json',Я.canonical(out));статус.textContent='Объяснение остатков подготовлено. Документы и история не изменены; внешнее исполнение не установлено.';}catch(e){статус.textContent='Объяснение не сформировано: '+e.message;}finally{завершитьДело();}});
    $('[data-action="копировать-документ"]').addEventListener('click',async()=>{try{if(!найти())throw Error('Документ не выбран.');await navigator.clipboard.writeText(найти().текст);статус.textContent='Текст документа скопирован.';}catch(e){статус.textContent='Копирование недоступно. Используйте сохранение текущего документа.';}});
    $('[data-action="сохранить-документ"]').addEventListener('click',()=>{const d=найти();if(d)скачать(`${d.идентификатор}.txt`,d.текст,'text/plain;charset=utf-8');});
    const corrForm=$('#форма-исправления');let получателей=0;
    const checkFields=(key,label,na=false)=>`<fieldset class="проверка-исправления" data-check="${key}"><legend>${label}</legend><label>Состояние<select data-part="состояние"><option>не проверено</option><option>подтверждено</option><option>не выполнено</option>${na?'<option>не применимо</option>':''}</select></label><label>Доказательная запись<textarea data-part="доказательство" rows="2"></textarea></label>${na?'<label>Основание неприменимости<textarea data-part="основание" rows="2"></textarea></label>':''}</fieldset>`;
    $('#проверки-исправления').innerHTML=checkFields('исходная_запись','Изменена ли исходная запись?')+checkFields('решение','Пересчитано ли решение?')+['доступ','положение','видимость','репутация'].map(k=>checkFields(k,`Восстановлено ли затронутое благо: ${k}?`,true)).join('');
    $('[data-action="добавить-получателя"]').addEventListener('click',()=>{
      const i=получателей++;const article=document.createElement('article');article.className='журнал-запись';article.dataset.recipient=String(i);
      article.innerHTML=`<label>Получатель ${i+1}<input data-recipient-name required></label>${checkFields(`уведомление-${i}`,'Уведомление получателя')}${checkFields(`исправление-${i}`,'Исправление производной записи')}<button type="button" class="вторичная" data-recipient-delete>Удалить незаписанного получателя</button>`;
      $('[data-recipient-delete]',article).addEventListener('click',()=>article.remove());$('#получатели-исправления').append(article);
    });
    corrForm.addEventListener('submit',e=>{
      e.preventDefault();const read=key=>{const root=$(`[data-check="${key}"]`,corrForm);return Object.fromEntries($$('[data-part]',root).map(x=>[x.dataset.part,x.value]));};
      const c={идентификатор:crypto.randomUUID(),запись:corrForm.elements.запись.value.trim(),дата:corrForm.elements.дата.value,исходная_запись:read('исходная_запись'),решение:read('решение'),все_получатели_перечислены:corrForm.elements.все_получатели.checked,основание_отсутствия_получателей:corrForm.elements.нет_получателей.value.trim(),получатели:$$('[data-recipient]',corrForm).map(r=>({название:$('[data-recipient-name]',r).value.trim(),уведомление:read(`уведомление-${r.dataset.recipient}`),исправление:read(`исправление-${r.dataset.recipient}`)})),восстановление:Object.fromEntries(['доступ','положение','видимость','репутация'].map(k=>[k,read(k)]))};
      if(!Я.date(c.дата)||c.дата>дело.дата_проверки){статус.textContent='Дата исправления недействительна или позже даты проверки дела.';return;}
      дело.исправления.push(c);событие('Добавлена проверка исправления: '+c.запись);показать();статусСмены(Я.correction(c).состояние+'. Предыдущие проверки сохранены в хронологии.');
    });
    corrForm.elements.дата.value=сегодня();
    $('#дело-точное-время').addEventListener('change',e=>{const v=e.target.value.trim();if(v&&!Я.time(v)){статус.textContent='Неверный точный момент UTC; срез не изменён.';e.target.value=дело.точное_время_проверки||'';return;}дело.точное_время_проверки=v||null;if(v)дело.дата_проверки=v.slice(0,10);показать();статусСмены('Временной срез пересчитан; поздние внесения не включены в ранний срез.');});
    const формаСобытия=$('#форма-события-исправления');
    const режимСостава=()=>{формаСобытия.elements.состав.disabled=формаСобытия.elements.действие.value!=='изменение состава исправления';};
    формаСобытия.elements.действие.addEventListener('change',режимСостава);режимСостава();
    $('#форма-события-исправления').addEventListener('submit',async e=>{
      e.preventDefault();if(делоЗанято)return;делоЗанято=true;const form=e.currentTarget,input=данныеФормы(form),expected=Я.canonical(дело),revision=правкаФормы;
      try{const next=await Я.appendCorrectionEvent(дело,{...input,состав:input.состав?.trim()?Я.parse(input.состав,16384):null,отменяет:input.отменяет.trim()||null,идентификатор:crypto.randomUUID(),внесено:сейчас(),предел_удостоверения:Я.EVENT_BOUNDARY});
        if(правкаФормы!==revision||Я.canonical(дело)!==expected||Я.canonical(данныеФормы(form))!==Я.canonical(input))throw Error('Дело или форма изменились; событие не внесено.');
        дело=structuredClone(next);событие('Добавлена запись событийного исправления: '+input.действие,input.документ);показать();статусСмены('Событие и его отпечаток добавлены. Внешнее исполнение и личность не удостоверены.');
      }catch(error){статус.textContent='Событие не внесено: '+error.message;}finally{делоЗанято=false;}
    });
    $('[data-action="вернуть-папку"]').addEventListener('click',()=>{if(делоЗанято||!предыдущаяПапка)return;if(confirm('Вернуть папку до последнего успешного импорта? Текущую сначала сохраните в файл.')){const current=дело;дело=предыдущаяПапка;предыдущаяПапка=current;выбран='';показать();статусСмены('Предыдущая папка восстановлена в памяти вкладки.');}});
    $('[data-action="подтвердить-сохранение"]').addEventListener('click',()=>{if(редакторИзменён){статус.textContent='Есть невнесённые поля редактора; сначала сохраните изменение документа.';return;}изменено=false;статус.textContent='Вы подтвердили наличие сохранённого файла. Программа его запись на диск не проверяла.';});
    $('[data-action="сброс-дела"]').addEventListener('click',()=>{if(делоЗанято){статус.textContent='Во время операции сброс недоступен.';return;}if(confirm('Удалить несохранённую папку из этой вкладки?')){изменено=false;location.reload();}});
    window.addEventListener('beforeunload',e=>{if(изменено){e.preventDefault();e.returnValue='';}});
    показать();
  }

  // ---------------- Требования и двусторонние связи ----------------
  async function запуститьТребования() {
    if(document.body.dataset.method!=='требования')return;
    const форма=$('#форма-требования');const статус=$('#статус-требований');const ключ='война-реестр-требований-4-9';const savedRegistry=прочитать(ключ,{записи:[],перенос:null});let записи=savedRegistry.записи||[];let переносРеестра=savedRegistry.перенос||null;let занято=false;
    const base=await получить('сущности/граф-связей.json');
    const initialErrors=await Я.registryErrors(записи);if(initialErrors.length){статус.textContent='Сохранённый реестр не принят: '+initialErrors.join(' ');записи=[];}
    const показать=()=>{
      const graph=Я.requirementGraph(записи,base);
      $('#журнал-требований').innerHTML=записи.length?записи.map(r=>{
        const id=`требование-${r.код}`;const nodes=graph.узлы.filter(n=>n.идентификатор.startsWith(id+'-'));
        return `<article class="журнал-запись" id="${id}"><h3>${безопасно(r.код)} · ${безопасно(r.требующая_сторона)}</h3><p>${безопасно(r.дата_начала)} · ${безопасно(r.класс_доказательства)}</p><p>${безопасно(r.основание)}</p><p>Условие опровержения: ${безопасно(r.опровержение)}</p><p>Источник: ${Я.safeURL(r.источник)?`<a href="${безопасно(r.источник)}" rel="noreferrer">${безопасно(r.источник)}</a>`:'Адрес не проверен.'}</p><nav aria-label="Связи записи ${безопасно(r.код)}">${nodes.map(n=>`<a href="#${n.идентификатор}">${безопасно(n.вид)}</a> `).join('')}</nav>${nodes.map(n=>`<section id="${n.идентификатор}"><h4>${безопасно(n.вид)}</h4><p>${безопасно(n.название)}${n.установлено?'':' · не подтверждено'}</p><a href="#${id}">Обратно к записи ${безопасно(r.код)}</a></section>`).join('')}</article>`;
      }).join(''):'<p>Проверяемых записей пока нет. Пустой реестр не доказывает отсутствия требований.</p>';
      $('#происхождение-реестра').textContent=переносРеестра?`Перенос из ${переносРеестра.происхождение.исходная_редакция}; исходный файл ${переносРеестра.происхождение.отпечаток_файла}; прежний ключ ${переносРеестра.отпечаток_старого_ключа}. Сборочный ключ не использован.`:'Реестр не переносился между версиями графа.';
      $('#звенья-реестра').replaceChildren();const note=document.createElement('p');note.textContent='Состав изменился или прочитан из локального хранилища. Для проверки цепочки используйте отдельную кнопку.';$('#звенья-реестра').append(note);
      $('#связи-требований').textContent=`Локальных записей: ${записи.length}. Каждая имеет шесть типизированных сущностей и двенадцать направленных связей. Неустановленные версия, политика или испытание остаются явно неизвестными.`;
    };
    форма.addEventListener('submit',async e=>{
      e.preventDefault();if(занято){статус.textContent='Повторное действие не принято: выполняется другая операция.';return;}занято=true;try{const r=данныеФормы(форма);
      if(!Я.safeURL(r.источник)){статус.textContent='Источник: допускается только адрес HTTP или HTTPS без встроенного пароля.';return;}
      const formBefore=Я.canonical(данныеФормы(форма));r.код=crypto.randomUUID();r.создано=сейчас();r.ша256=await отпечаток(r);if(Я.canonical(данныеФормы(форма))!==formBefore)throw Error('Форма изменилась во время обработки; запись не добавлена.');const errs=await Я.registryErrors([...записи,r]);if(errs.length){статус.textContent=errs.join('\n');return;}if(Я.canonical(данныеФормы(форма))!==formBefore)throw Error('Форма изменилась во время проверки; запись не добавлена.');записи.push(r);
      const saved=записать(ключ,{записи,перенос:переносРеестра});форма.reset();показать();статус.textContent=saved?'Запись и двусторонние связи сохранены в этом браузере. Публикация в общий реестр не выполнена.':'Запись есть только в оперативной памяти. Сохраните реестр в файл.';
      }catch(error){статус.textContent='Запись не добавлена: '+error.message;}finally{занято=false;}
    });
    $('[data-action="экспорт-требований"]').addEventListener('click',async()=>{
      if(занято)return;занято=true;
      try{const out=await Я.exportRegistry(записи,base,null,переносРеестра);скачать('реестр-требований-и-связи-4.9.0-wip.json',Я.canonical(out)+'\n');статус.textContent=`Реестр подписан локальным ключом сеанса. SHA-256 производной: ${out.производная.ша256}. Это не подпись владельца и не удостоверение личности.`;}
      catch(e){статус.textContent='Подписанный экспорт не сформирован: '+e.message;}finally{занято=false;}
    });
    $('#импорт-требований').addEventListener('change',async e=>{
      const file=e.target.files[0];if(!file||занято)return;занято=true;
      try{if(file.size>25*1024*1024)throw Error('Реестр превышает 25 МиБ.');const raw=await file.arrayBuffer(),input=Я.parse(new TextDecoder('utf-8',{fatal:true}).decode(raw),25*1024*1024);let restored;
        if(input.редакция_схемы===редакция&&input.производная?.отпечаток_базового_графа===await Я.hash(Я.canonical(base)))restored=await Я.restoreRegistry(input,base);
        else {const selected=$('#исходный-граф-требований').files[0];let graphBytes;
          if(selected){if(selected.size>25*1024*1024)throw Error('Прежний граф превышает 25 МиБ.');graphBytes=await selected.arrayBuffer();}
          else {const reply=await fetch('данные/базовый-граф-4.4.0.json',{cache:'no-store'});if(!reply.ok)throw Error('Не загружен прежний базовый граф; выберите его файл явно.');graphBytes=await reply.arrayBuffer();}
          restored=await Я.migrateRegistryBytes(raw,file.name,base,graphBytes,selected?.name||'базовый-граф-4.4.0.json');
        }
        if(записи.length&&!confirm('Заменить рабочий реестр проверенным импортом? Несохранённые записи будут потеряны.'))return;
        записи=restored.записи;переносРеестра=restored.перенос||null;const saved=записать(ключ,{записи,перенос:переносРеестра});показать();статус.textContent=`Подпись источника и повторный расчёт проверены; при переносе сохранены старый граф и подписанные байты. Импортировано записей: ${записи.length}. ${restored.граница} ${saved?'Локальная запись выполнена.':'Хранилище недоступно; экспортируйте работу.'}`;
      }catch(error){статус.textContent='Импорт отклонён; рабочий реестр сохранён. '+error.message;}finally{занято=false;e.target.value='';}
    });
    let отменаЦепочки=null;$('#отменить-цепочку').addEventListener('click',()=>{отменаЦепочки?.abort();статус.textContent='Запрошена отмена проверки цепочки; реестр не изменяется.';});
    $('#проверить-цепочку-реестра').addEventListener('click',async()=>{
      if(занято)return;занято=true;отменаЦепочки=new AbortController();$('#отменить-цепочку').disabled=false;
      try{const origin=переносРеестра,rows=await задача('registry-history',origin,{signal:отменаЦепочки.signal,envelope:true,isCurrent:()=>origin===переносРеестра}),node=$('#звенья-реестра');await Я.checkpoint(отменаЦепочки.signal);if(origin!==переносРеестра)throw Error('Цепочка изменилась во время проверки.');node.replaceChildren();
        if(!rows.length){node.textContent='Сохранённых звеньев переноса нет. Это не внешнее удостоверение.';return;}
        for(let i=0;i<rows.length;i++){
          const r=rows[rows.length-1-i],article=document.createElement('article');article.className='журнал-запись';
          const info=document.createElement('p');info.textContent=`${r.редакция}; исходный файл ${r.отпечаток_файла}; ключ ${r.отпечаток_ключа}.`;article.append(info);
          for(const graph of [false,true]){const button=document.createElement('button');button.type='button';button.textContent=graph?'Передать исходный граф браузеру':'Передать точную подписанную оболочку браузеру';
            button.addEventListener('click',async()=>{if(занято)return;занято=true;отменаЦепочки=new AbortController();$('#отменить-цепочку').disabled=false;try{if(origin!==переносРеестра)throw Error('Рабочая цепочка изменилась.');const link=await задача('registry-link',{migration:origin,index:i},{signal:отменаЦепочки.signal,envelope:true,isCurrent:()=>origin===переносРеестра});await Я.checkpoint(отменаЦепочки.signal);if(origin!==переносРеестра)throw Error('Цепочка изменилась перед передачей.');скачать(graph?link.имя_графа:link.имя,graph?link.граф:link.байты,'application/octet-stream');статус.textContent='Точные исходные байты переданы браузеру; проверьте сохранённый файл.';}catch(e){статус.textContent='Исходник не передан: '+e.message;}finally{занято=false;отменаЦепочки=null;$('#отменить-цепочку').disabled=true;}});article.append(button);}
          node.append(article);
        }
        статус.textContent=`Проверено звеньев: ${rows.length}. Подписи и графы совпали; полномочия владельца не удостоверены.`;
      }catch(error){статус.textContent='Цепочка не принята: '+error.message;}finally{занято=false;отменаЦепочки=null;$('#отменить-цепочку').disabled=true;}
    });
    $('[data-action="сброс-требований"]').addEventListener('click',()=>{if(занято){статус.textContent='Очистка не выполнена: выполняется другая операция.';return;}if(confirm('Очистить локальный реестр?')){if(!записать(ключ,{записи:[],перенос:null})){статус.textContent='Очистка не выполнена: хранилище недоступно; текущий реестр сохранён.';return;}записи=[];переносРеестра=null;показать();статус.textContent='Локальный реестр очищен.';}});
    показать();
  }

  let draftPreview=null;
  $('#предпросмотр-черновика')?.addEventListener('click',async e=>{
    if(!confirm('Прочитать точный черновик выбранной редакции для предварительного просмотра? Новый ключ пока не записывается.'))return;
    const button=e.currentTarget;button.disabled=true;
    try{draftPreview=await Я.previewStorageMigration(localStorage,button.dataset.key.replace(/4-5$/, $('#редакция-черновика')?.value||'4-5'),true);const view={...draftPreview,исходник:{...draftPreview.исходник}};delete view.исходник.база64;$('#результат-предпросмотра').textContent=JSON.stringify(view,null,2);$('#применить-черновик').disabled=false;$('#сохранить-черновик-источник').disabled=false;}
    catch(error){draftPreview=null;$('#применить-черновик').disabled=true;$('#сохранить-черновик-источник').disabled=true;$('#результат-предпросмотра').textContent='Преобразование не подготовлено: '+error.message;}
    finally{button.disabled=false;}
  });
  $('#сохранить-черновик-источник')?.addEventListener('click',()=>{if(draftPreview)скачать(draftPreview.исходник.имя,Я.unbase64(draftPreview.исходник.база64,40*1024*1024),'application/octet-stream');});
  $('#применить-черновик')?.addEventListener('click',()=>{
    if(!draftPreview||!confirm('Записать проверенный черновик в новый ключ и перезагрузить страницу? Текущее значение нового ключа будет заменено; старый ключ не удаляется. Несохранённые поля этой страницы сначала сохраните отдельно.'))return;
    try{Я.commitStorageMigration(localStorage,draftPreview,true);draftPreview=null;$('#применить-черновик').disabled=true;$('#сохранить-черновик-источник').disabled=true;$('#результат-предпросмотра').textContent='Новый ключ записан; старый сохранён. Запрошена перезагрузка. При её отказе текущая страница ещё показывает прежнюю рабочую версию.';try{location.reload();}catch(error){$('#результат-предпросмотра').textContent='Новый ключ записан, но перезагрузка не выполнена: '+error.message;}}catch(error){$('#результат-предпросмотра').textContent='Запись нового ключа не выполнена: '+error.message;}
  });
  const previousStorageButton=$('#предыдущее-хранилище');
  previousStorageButton?.addEventListener('click',async()=>{
    if(!confirm('Прочитать прежнее локальное значение и передать его точные байты в файл? Исходное значение не удаляется и не заменяется. Это ещё не импорт в новую рабочую схему.'))return;
    try{const old=await Я.inspectPreviousStorage(localStorage,previousStorageButton.dataset.key,true);скачать(old.исходник.имя,Я.unbase64(old.исходник.база64,40*1024*1024),'application/json');узелСтатуса().textContent=old.граница+' SHA-256: '+old.исходник.ша256+'. Файл передан браузеру; проверьте сохранение.';}catch(error){сообщитьОшибку(error);}
  });
  получить('данные/профиль-схем-4.9.0.json').then(async profile=>{
    const entries=await Promise.all(Object.entries(profile.файлы).map(async([k,v])=>[k,await получить(v)]));
    Я.configureSchemas(Object.fromEntries(entries));
    return Promise.allSettled([
    запуститьАудит(),
    запуститьСимметрию(),
    запуститьОтказы(),
    Promise.resolve(запуститьОбжалование()),
    Promise.resolve(запуститьТребования()),
    запуститьЖизненныйЦикл(),
    запуститьГраф()
  ]); }).then(результаты => {
    const ошибка=результаты.find(з=>з.status==='rejected');
    if(ошибка) throw ошибка.reason;
  }).catch(ошибка=>{console.error(ошибка);const node=узелСтатуса();node.textContent='Инструмент не запущен: '+ошибка.message;});
})();
