from pathlib import Path
R=Path('/mnt/data/work4100/site');p=R/'ресурсы/сценарии/доказательный-контур.js';s=p.read_text()
s=s.replace("a.редакция_схемы === '4.8.0-wip' ? schemas.audit480 : schemas.audit", "a.редакция_схемы === '4.8.0-wip' ? schemas.audit480 : a.редакция_схемы === '4.9.0-wip' ? schemas.audit490 : schemas.audit")
s=s.replace("'война/односторонний-перенос/4.8.0-1','война/односторонний-перенос/4.10.0-1'", "'война/односторонний-перенос/4.8.0-1','война/односторонний-перенос/4.9.0-1','война/односторонний-перенос/4.10.0-1'")
s=s.replace("candidate?.запись?.редакция_схемы==='4.8.0-wip'?schemas.callReceipt480:schemas.callReceipt", "candidate?.запись?.редакция_схемы==='4.8.0-wip'?schemas.callReceipt480:candidate?.запись?.редакция_схемы==='4.9.0-wip'?schemas.callReceipt490:schemas.callReceipt")
# Release the duplicate parsed source only after exact semantic equality; no mutation of a caller-owned envelope.
old='''      items.push(s);
    }
    const decisions=[];'''
new='''      if(!same(s,x))throw Error(`Исходник ${x.имя}: представление расходится с повторным приёмом байтов.`);
      // Здесь envelope принадлежит только восстановлению: клон публичного входа
      // либо частный результат разбора. Равная замена освобождает второй граф
      // источника; исходные байты, отпечаток и последующая проверка не меняются.
      envelope.источники[items.length]=s;
      items.push(s);
    }
    const decisions=[];'''
assert old in s;s=s.replace(old,new,1)
# Transport references are separate from logical entities, still bounded explicitly.
s=s.replace('questions:4096,conflicts:2048,expansionBytes:', 'questions:4096,conflicts:2048,questionReferences:32768,conflictReferences:16384,expansionBytes:')
s=s.replace('function resourceCheck(value,{files=false,envelope=false}={})', 'function resourceCheck(value,{files=false,envelope=false,representation=false}={})')
s=s.replace('if(questions>p.questions||conflicts>p.conflicts)', 'if(questions>(representation?p.questionReferences:p.questions)||conflicts>(representation?p.conflictReferences:p.conflicts))')
s=s.replace("resourceCheck(value,{envelope:true});\n    const obj", "resourceCheck(value,{envelope:true,representation:true});\n    const obj")
# Exact canonical size without constructing escaped string tokens or an output buffer.
insert=r'''
  function canonicalMeasure(value,limit=CANONICAL_BYTES_LIMIT){
    if(!Number.isSafeInteger(limit)||limit<0)throw Error('Некорректный предел канонического размера.');
    let bytes=0,nodes=0;const active=new WeakSet();
    const add=n=>{bytes+=n;if(bytes>limit)throw Error('Ограничительный профиль: канонические байты превысят '+limit+' до сериализации.');};
    function stringSize(str){
      add(2);
      for(let i=0;i<str.length;i++){
        const c=str.charCodeAt(i);
        if(c===34||c===92||[8,9,10,12,13].includes(c))add(2);
        else if(c<32)add(6);
        else if(c<128)add(1);
        else if(c<2048)add(2);
        else if(c>=0xd800&&c<=0xdbff){
          const lo=str.charCodeAt(i+1);if(lo>=0xdc00&&lo<=0xdfff){add(4);i++;}else add(6);
        }else if(c>=0xdc00&&c<=0xdfff)add(6);else add(3);
      }
    }
    function visit(v,depth=0,array=false){
      if(depth>40||++nodes>1000000)throw Error('Предел измерения канонизации: глубина или число значений.');
      if(typeof v==='string'){stringSize(v);return;}
      if(v===null){add(4);return;}
      if(typeof v!=='object'){
        if(typeof v==='number'&&!Number.isFinite(v))throw Error('Недопустимое число.');
        const token=JSON.stringify(v);add(token===undefined?(array?4:0):token.length);return;
      }
      if(active.has(v))throw Error('Циклический объект не является JSON.');active.add(v);
      if(Array.isArray(v)){add(2);for(let i=0;i<v.length;i++){if(i)add(1);visit(v[i],depth+1,true);}}
      else {add(2);let first=true;for(const key of Object.keys(v)){
        const descriptor=Object.getOwnPropertyDescriptor(v,key);
        if(!descriptor||!Object.hasOwn(descriptor,'value'))throw Error('Вычисляемое поле не допускается при измерении.');
        const item=descriptor.value;if(item===undefined||typeof item==='function'||typeof item==='symbol')continue;
        if(!first)add(1);first=false;stringSize(key);add(1);visit(item,depth+1);
      }}active.delete(v);
    }
    visit(value);return bytes;
  }
  // Разделяет сущности дела, повторные представления и транспорт. Оценка
  // расширения консервативна для перечисленных форм, но не является пределом RSS.
  function operationBudget(command,data){
    const transport=resourceCheck(data,{envelope:true,representation:true}),p=INTERACTIVE_PROFILE;
    let logicalQuestions=0,outputBytes=null,extra=0;
    const caseCommand=['case-export','case-transfer','case-explanation'].includes(command);
    if(caseCommand){
      if(!Array.isArray(data?.документы)||!Array.isArray(data?.исправления))throw Error('Для оценки дела нужны документы и исправления.');
      for(const d of data.документы)logicalQuestions+=(d.вопросы?.length||0)+(d.ответы_на_вопросы?.length||0);
      if(logicalQuestions>p.questions)throw Error('Ограничительный профиль: более 4096 текущих вопросов и связей ответов.');
      const inputBytes=canonicalMeasure(data,p.envelopeBytes);
      // История вопроса в срезах и повтор в остатках не являются новыми вопросами.
      // Шесть представлений и фиксированный запас на поля итогов ограничивают
      // вычисление до clone/coverage; схемы, подпись и выходная проверка остаются.
      outputBytes=inputBytes*6+data.документы.length*4096;
      extra=transport.оценка_расширения*3+data.документы.length*4096;
      if(command==='case-explanation'){
        let references=0;
        for(const r of data.исправления){
          const events=(data.события_исправления||[]).filter(e=>e.исправление===r.идентификатор);
          const recipients=new Set((r.получатели||[]).map(x=>x.название));
          for(const e of events)for(const name of e.состав?.получатели||[])recipients.add(name);
          references+=(6+2*recipients.size)*events.length;
        }
        // Каждая опора несёт причины и ссылки; это явный консервативный запас.
        extra+=references*4096;outputBytes+=references*2048;
      }
    }else if(command==='canonical')outputBytes=canonicalMeasure(data,p.envelopeBytes);
    else if(command==='seal'||command==='panel-transfer')outputBytes=canonicalMeasure(data,p.envelopeBytes)+256;
    if(outputBytes!==null&&outputBytes>p.envelopeBytes)throw Error('Ограничительный профиль: ранняя оценка производного представления превышает 32 МиБ; вычисление не начато.');
    const estimate=transport.оценка_расширения+extra+(outputBytes||0);
    if(estimate>p.expansionBytes)throw Error('Ограничительный профиль: ранняя оценка рабочей операции превышает 96 МиБ; вычисление не начато.');
    return {профиль:p.id,команда:command,текущих_вопросов_и_ответов:logicalQuestions,
      повторных_представлений_вопросов:transport.вопросов,предварительные_байты_выхода:outputBytes,
      оценка_рабочих_представлений:estimate,граница:'Числа логических полей, точных канонических байтов и консервативного расширения различимы. Это не RSS и не гарантия устройства; выход проверяется повторно.'};
  }
'''
marker='  function correctionExplanation(c,at=undefined){';assert marker in s;s=s.replace(marker,insert+'\n'+marker)
# Preflight only for explicitly requested interactive operations, after immutable snapshot in core entry points.
s=s.replace('c=structuredClone(c);await stage(options,"снимок дела");','if(options.jsonBudget)operationBudget(\'case-export\',c);\n    c=structuredClone(c);await stage(options,"снимок дела");')
s=s.replace('const API = {canonicalBytesEqual,','const API = {canonicalMeasure,operationBudget,canonicalBytesEqual,')
p.write_text(s)
# Workers preflight data before computing/expanding. Command data with Blobs keeps transport-only bounds.
p=R/'ресурсы/сценарии/поток-доказательного-контура.js';s=p.read_text();s=s.replace('K.resourceCheck(data,{envelope:true});stage', 'K.operationBudget(command,data);stage');p.write_text(s)
