{
  "revision": "0.20",
  "profile": "open-alert-draft/0.20",
  "updated": "12 октября 2026",
  "documentInfo": {
    "title": "Открытый протокол систем оповещения. Техническая спецификация",
    "organization": "ООО \"СОТКОМ-ЮГ\"",
    "status": "Проект стандарта организации. Для публичного обсуждения",
    "designation": "Обозначение СТО не присвоено",
    "approval": "Не утвержден. Дата введения в действие не установлена",
    "canonical": "https://gksotcom.ru/open-protocol/"
  },
  "normativeReferences": [
    [
      {
        "text": "Федеральный закон от 29.06.2015 № 162-ФЗ, статья 21",
        "href": "https://www.consultant.ru/document/cons_doc_LAW_181810/2ff881c0e2bdab3d42c301c42aeaa00b6edcd56f/"
      },
      "Правовая основа разработки, утверждения, изменения и применения СТО. Порядок устанавливает организация. Экспертиза техническим комитетом и регистрация в фонде стандартов не объявляются выполненными."
    ],
    [
      {
        "text": "ГОСТ Р 1.4-2004. Стандартизация в Российской Федерации. Стандарты организаций. Общие положения",
        "href": "https://protect.gost.ru/gost/details/0608066d-4935-41c1-91b6-59d494c6df8c"
      },
      "Основа организации разработки и применения стандарта организации."
    ],
    [
      {
        "text": "ГОСТ Р 1.5-2012. Стандартизация в Российской Федерации. Стандарты национальные. Правила построения, изложения, оформления и обозначения",
        "href": "https://protect.gost.ru/gost/details/86d71344-7364-40a3-bd71-a1b8a5b3746e"
      },
      "Для файловой версии проекта выбраны правила структуры, нумерации, изложения требований, таблиц, приложений и ссылок. Область самого ГОСТ - национальные и предварительные национальные стандарты; применение выбранных правил к СТО закрепляется при утверждении в организации."
    ]
  ],
  "sections": [
    {
      "id": "status",
      "title": "1. Назначение и статус документа",
      "blocks": [
        {
          "type": "p",
          "text": "Настоящий документ представляет собой техническую спецификацию Открытого протокола систем оповещения. Он предназначен для разработчиков оборудования и программного обеспечения, проектировщиков и испытательных организаций. Документ устанавливает форматы сообщений, правила управления, передачи речи и контроля исполнения, а также критерии совместимости независимых реализаций."
        },
        {
          "type": "p",
          "text": "Открытый протокол систем оповещения задает правила взаимодействия АРМ, устройств управления и оконечного оборудования любых производителей. Протокол устанавливает общие команды, модель состояний, профили возможностей и критерии совместимости. Спецификация не устанавливает торговые наименования, изготовителя или конкретную аппаратную платформу."
        },
        {
          "type": "note",
          "text": "Настоящая спецификация устанавливает профиль open-alert-draft/0.20, правила управления конечными исполнениями, телеметрии, речи и сервисных операций. Нормативными являются требования, форматы, диапазоны, состояния, переходы и критерии результата."
        },
        {
          "type": "heading",
          "id": "status-terms",
          "text": "Как читать требования"
        },
        {
          "type": "p",
          "text": "Обязательные требования выражаются ключевыми словами «ДОЛЖЕН», «ДОЛЖНА», «ДОЛЖНО», «ДОЛЖНЫ», «НЕОБХОДИМО», «ТРЕБУЕТСЯ»; запреты — «НЕ ДОПУСКАЕТСЯ», «ЗАПРЕЩАЕТСЯ», «НЕ ДОЛЖЕН» с соответствующим согласованием. «РЕКОМЕНДУЕТСЯ» означает рекомендацию, отступление от которой требует обоснования; «ДОПУСКАЕТСЯ» и «РАЗРЕШАЕТСЯ» — разрешенную возможность. Ключевые слова в нормативных положениях выделяются прописными буквами; это правило оформления настоящего документа. Сочетание «НЕ ТРЕБУЕТСЯ» означает отсутствие обязанности, а не запрет. Установленные форматы, диапазоны, состояния и правила в повествовательной форме также обязательны, если прямо не обозначены как рекомендации или примеры. Обязательность относится к указанному профилю и проверяется при оценке соответствия реализации."
        },
        {
          "type": "heading",
          "id": "status-roles",
          "text": "Участники и термины"
        },
        {
          "type": "p",
          "text": "АРМ - автоматизированное рабочее место управления системой оповещения любого производителя, включая его серверные компоненты. Совместимая реализация ДОЛЖНА соблюдать требования заявленных профиля и редакции. nodeId обозначает логический узел; экземпляр серверного процесса, защищенное подключение и оператор имеют отдельные идентичности. Конфигуратор является отдельной ролью, а не обязательной частью конкретного АРМ."
        },
        {
          "type": "p",
          "text": "УУ - устройство управления: участник сетевого обмена, который принимает команды, ведет сеансы и взаимодействует с оконечным устройством (ОУ). ВАУ и Сирена обозначают функциональные типы ОУ, а не модели изделий. УУ может быть отдельным контроллером или входить в состав ОУ; разделение сетевой и исполнительной частей сохраняется в модели протокола."
        },
        {
          "type": "table",
          "headers": [
            "Термин",
            "Краткое определение"
          ],
          "rows": [
            [
              "АРМ / УУ / ОУ",
              "Узел управления / сетевой исполнитель команд / оконечное оборудование."
            ],
            [
              "ВАУ / Сирена",
              "Функциональные типы оконечного оборудования."
            ],
            [
              "targetId / deviceId",
              "Логическая цель в системе / идентичность физического сетевого устройства."
            ],
            [
              "principalId / controlEpoch",
              "Участник управления / непрозрачный идентификатор текущего владения."
            ],
            [
              "capabilityRevision / stateRevision",
              "Ревизия каталога возможностей и точек / ревизия наблюдаемого состояния."
            ],
            [
              "sourceKind / evidence",
              "Вид источника показания / уровень доказательства физического результата."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "status-maturity",
          "text": "Обязательность требований"
        },
        {
          "type": "table",
          "headers": [
            "Тип требования",
            "Применение"
          ],
          "rows": [
            [
              "Обязательное",
              "Все реализации выбранного профиля соблюдают его команды, форматы, состояния и запреты."
            ],
            [
              "Условно обязательное",
              "При объявлении дополнительной функции обязательны весь ее контракт и зависимости."
            ],
            [
              "Параметр изделия",
              "Значение указывается в паспорте в установленном диапазоне; отсутствие обязательного значения запрещает использование функции."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "gost-position",
          "text": "Соотношение с ГОСТ Р 42.3.05-2023"
        },
        {
          "type": "p",
          "text": "ГОСТ Р 42.3.05-2023 устанавливает информационный обмен технических средств оповещения населения. Его приложения описывают разные интерфейсы; восьмибайтовый основной кадр приложения Б не характеризует весь стандарт. Настоящая спецификация задает самостоятельный формат и не является реализацией приложений А, Б или В этого ГОСТ."
        },
        {
          "type": "p",
          "text": "При заявлении соответствия ГОСТ Р 42.3.05-2023 выполняются требования его пункта 5.2: обязательная поддержка ИП с предусмотренным исключением для производителей оконечных средств и ограничение назначения дополнительных протоколов. Замена ИП командами настоящего протокола на требуемом интерфейсе АРМ–УУ не удовлетворяет этому пункту. Одновременное наличие другого порта с ИП, шлюза или названия «дополняющий протокол» само по себе не устраняет несоответствие назначения дополнительного обмена."
        },
        {
          "type": "p",
          "text": "Пункты 5.1 и 5.3 отделяют информационный протокол от механизмов транспорта, маршрутизации, резервирования и защиты. Эти функции рассматриваются отдельно от альтернативного кодирования session.start, session.stop и квитанций. Передача файлов до оповещения относится к сервисному обмену лишь тогда, когда она не подменяет формирование и передачу сигналов, команд и квитанций обязательного ИП."
        },
        {
          "type": "table",
          "headers": [
            "Граница применения",
            "Правило проекта"
          ],
          "rows": [
            [
              "АРМ–АРМ, АРМ–УУ и сопряжение с вещанием по ГОСТ",
              "Требуемый интерфейс выполняется по соответствующему приложению ГОСТ. Преобразователь проверяет каждое отображаемое действие и не объявляет форматы побайтово совместимыми."
            ],
            [
              "УУ–ОУ и встроенный контроллер ОУ",
              "Применимость исключения для изготовителя оконечных средств определяется реальной функцией изделия. Переименование устройства управления в оконечное средство не меняет требований к его внешнему интерфейсу."
            ],
            [
              "Объектовая система и иные области",
              "Область определяется назначением системы и применимыми требованиями, включая пожарные. Отсутствие данного интерфейса в ГОСТ Р 42.3.05-2023 само по себе не освобождает от других обязательных требований."
            ],
            [
              "Защита, маршрутизация и обслуживание",
              "Оцениваются отдельно в пределах пунктов 5.1–5.3. Их поддержка не доказывает соответствие предметных команд приложениям ГОСТ."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Проект сопряжения содержит матрицу «требование ГОСТ — интерфейс — команда — подтверждение — испытание». Для приема команды, фактического задействования и выключения фиксируются раздельные доказательства. Автоматическая квитанция не подменяется успешной доставкой TCP, а отсутствующее в нижнем протоколе подтверждение не создается преобразователем. Уровень доказательства сохраняется при преобразовании."
        },
        {
          "type": "p",
          "text": "Статья 26 Федерального закона № 162-ФЗ устанавливает общее правило добровольного применения документов стандартизации и случаи обязательности, включая публичное заявление о соответствии. Применимость требований к конкретной системе определяется также законодательством, заданием и обязательствами поставки. Несоответствие выбранному ГОСТ не равнозначно общему законодательному запрету самостоятельного протокола; настоящий документ не разрешает отступление от обязательного для проекта интерфейса."
        },
        {
          "type": "table",
          "id": "publication-metadata",
          "headers": [
            "Сведения о документе",
            "Значение"
          ],
          "rows": [
            [
              "Наименование",
              "Открытый протокол систем оповещения. Техническая спецификация"
            ],
            [
              "Организация-разработчик",
              "ООО \"СОТКОМ-ЮГ\""
            ],
            [
              "Статус",
              "Проект стандарта организации. Для публичного обсуждения"
            ],
            [
              "Обозначение",
              "Обозначение СТО не присвоено"
            ],
            [
              "Утверждение",
              "Не утвержден. Дата введения в действие не установлена"
            ]
          ]
        },
        {
          "type": "p",
          "id": "publication-scope",
          "text": "Настоящий документ подготовлен как проект СТО. Его размещение для обсуждения не является утверждением стандарта организации или национального стандарта. Файловая и онлайн-редакции используют общие технические требования, схемы и примеры; различаются их оформление и навигация."
        },
        {
          "type": "table",
          "id": "publication-references",
          "headers": [
            "Основание оформления",
            "Применение"
          ],
          "rows": [
            [
              {
                "text": "Федеральный закон от 29.06.2015 № 162-ФЗ, статья 21",
                "href": "https://www.consultant.ru/document/cons_doc_LAW_181810/2ff881c0e2bdab3d42c301c42aeaa00b6edcd56f/"
              },
              "Правовая основа разработки, утверждения, изменения и применения СТО. Порядок устанавливает организация. Экспертиза техническим комитетом и регистрация в фонде стандартов не объявляются выполненными."
            ],
            [
              {
                "text": "ГОСТ Р 1.4-2004. Стандартизация в Российской Федерации. Стандарты организаций. Общие положения",
                "href": "https://protect.gost.ru/gost/details/0608066d-4935-41c1-91b6-59d494c6df8c"
              },
              "Основа организации разработки и применения стандарта организации."
            ],
            [
              {
                "text": "ГОСТ Р 1.5-2012. Стандартизация в Российской Федерации. Стандарты национальные. Правила построения, изложения, оформления и обозначения",
                "href": "https://protect.gost.ru/gost/details/86d71344-7364-40a3-bd71-a1b8a5b3746e"
              },
              "Для файловой версии проекта выбраны правила структуры, нумерации, изложения требований, таблиц, приложений и ссылок. Область самого ГОСТ - национальные и предварительные национальные стандарты; применение выбранных правил к СТО закрепляется при утверждении в организации."
            ]
          ]
        },
        {
          "type": "p",
          "id": "publication-editions",
          "text": "Редакция документа, версия wire-профиля и версия реализации учитываются раздельно. Номер документа и дата указываются в файле и онлайн-версии; комплект редакции связывает текст, схемы, словари, примеры и проверки контрольными суммами. Изменение технического требования отражается в журнале редакций. До утверждения сохраняется пометка проекта; обозначение СТО, приказ и дату введения присваивает организация."
        },
        {
          "type": "heading",
          "id": "publication-license",
          "text": "Открытая безвозмездная лицензия на материалы протокола, редакция 3"
        },
        {
          "type": "p",
          "text": "1. Лицензиар — ООО «СОТКОМ-ЮГ». По статье 1286.1 Гражданского кодекса Российской Федерации лицензиар предоставляет любому лицу простую (неисключительную) безвозмездную лицензию на опубликованные им текст спецификации «Открытый протокол систем оповещения», схемы, словари, примеры, тестовые векторы и исходные тексты проверок, прямо сопровождаемые этой лицензией. Сторонние произведения, на которые даны ссылки, в предмет лицензии не входят."
        },
        {
          "type": "p",
          "text": "2. Разрешены воспроизведение полностью и частично на любых носителях, распространение экземпляров, доведение до всеобщего сведения, публичный показ, перевод, переработка, включение в состав других произведений и программ, использование исходных и переработанных материалов в коммерческой и некоммерческой деятельности. Разрешено создавать и распространять производные материалы и предоставлять права на них, включая включенные исходные материалы, в пределах перечисленных прав. Отдельное разрешение и выплата вознаграждения не требуются."
        },
        {
          "type": "p",
          "text": "3. Использование разрешено без территориальных ограничений. Срок — весь срок действия исключительного права на соответствующий материал. Совершение любого из перечисленных в пункте 2 действий после ознакомления с условиями является акцептом лицензии; письменная форма договора при этом считается соблюденной. Простое чтение документа не возлагает дополнительных обязанностей."
        },
        {
          "type": "p",
          "text": "4. Обязательное размещение фирменного наименования, логотипа, ссылки на лицензиара или рекламной подписи в сетевых сообщениях, программах, изделиях и их интерфейсах не устанавливается. При передаче копии лицензируемых материалов получателю предоставляются настоящие условия; допустимо включить их в сопроводительный файл. Измененная спецификация обозначается как измененная, чтобы ее нельзя было принять за исходную редакцию. Личные неимущественные права авторов, охраняемые законом, сохраняются; эта лицензия не является отказом от них."
        },
        {
          "type": "p",
          "text": "5. Независимая реализация описанного метода обмена не требует заключения договора с разработчиком спецификации, членства в организации или раскрытия исходного кода реализации. По пункту 5 статьи 1259 ГК РФ идеи, методы, процессы, системы, способы и технические решения сами по себе не охраняются авторским правом. Лицензия распространяется на охраняемую форму материалов и не устанавливает исключительного права на сам способ обмена."
        },
        {
          "type": "p",
          "text": "6. Лицензия не предоставляет права на товарные знаки третьих лиц и не означает сертификацию, поддержку или одобрение конкретного изделия. Лицензиар предоставляет права только в отношении принадлежащих ему материалов. Самостоятельная реализация может распространяться на выбранных ее правообладателем условиях."
        },
        {
          "type": "p",
          "text": "7. Материалы предоставляются в опубликованном состоянии. Совместимость и пригодность реализации проверяются испытаниями; бесплатное предоставление материалов само по себе не является обязательством сопровождения или гарантией работоспособности оборудования. Ограничения ответственности применяются только в пределах, допускаемых законодательством Российской Федерации. К лицензии применяется право Российской Федерации."
        },
        {
          "type": "p",
          "text": "8. При соблюдении настоящих условий предоставленные права сохраняются на весь установленный срок и не могут быть отозваны по усмотрению лицензиара. Снятие материалов с публикации, замена редакции, изменение условий для последующих публикаций или прекращение сопровождения не прекращают ранее предоставленные права. Прекращение либо ограничение договора допускается только по основаниям и в порядке, которые прямо установлены применимыми императивными нормами закона. Пункт 4 статьи 1286.1 ГК РФ относится к предоставлению прав за пределами открытой лицензии и не является общим правом произвольного отзыва у соблюдающего условия пользователя."
        },
        {
          "type": "p",
          "text": "9. В отношении принадлежащих ему патентных прав, без использования которых невозможно выполнить обязательные требования данной опубликованной редакции, лицензиар принимает безвозмездное обязательство предоставить любому реализатору простую неисключительную лицензию на изготовление, применение, предложение к продаже, продажу, ввоз и иное введение соответствующих изделий в оборот, а также использование соответствующего способа. Объем ограничен необходимыми требованиями этой редакции; территория и срок соответствуют территории и оставшемуся сроку действия конкретного патента. Лицензиар не предъявляет к добросовестному реализатору требования о платежах или прекращении такого использования на основании контролируемых им необходимых патентных прав и обязуется оформить требуемый письменный договор без лицензионных платежей и ограничения круга реализаторов. После установления конкретного патента указываются его номер, существенные пункты формулы и способы использования; стороны выполняют государственную регистрацию предоставления права в случаях статей 1232 и 1369 ГК РФ. Настоящий пункт не подменяет требуемую регистрацию уже состоявшимся предоставлением права и не распоряжается патентами третьих лиц."
        },
        {
          "type": "p",
          "text": "10. Название протокола допускается использовать для достоверного указания поддерживаемой редакции и профиля. Измененный несовместимый вариант обозначается отдельно и не выдается за эту редакцию. Упоминание совместимости не означает одобрения или сертификации лицензиаром. Этот документ не удостоверяет регистрацию названия в качестве товарного знака и не устанавливает исключительного права на описательное словосочетание."
        },
        {
          "type": "p",
          "text": "Правовая основа условий использования: ГК РФ, часть четвертая, статьи 1232, 1235, 1236, 1259, 1265, 1286.1, 1367 и 1369. Полный русский текст лицензии приведен здесь и в файле license-0.20-ru.txt; обращение к иностранному тексту лицензии не требуется."
        },
        {
          "type": "table",
          "caption": "Правовая основа лицензии",
          "headers": [
            "Правовой источник",
            "Применение"
          ],
          "rows": [
            [
              {
                "text": "ГК РФ, часть четвертая — Роспатент",
                "href": "https://rospatent.gov.ru/ru/documents/grazhdanskiy-kodeks-rossiyskoy-federacii-chast-chetvertaya"
              },
              "Пределы авторского права и открытая лицензия."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Нормативный комплект фиксируется файлами specification, vocabulary, schemas и vectors, связанными контрольными суммами манифеста публикации. JS служит редактируемым источником, но реализация не обязана выполнять JavaScript. Форматирование чисел детерминировано и не использует Intl/локаль системы; генерация JSON сравнивается при разных LC_ALL и TZ. Побайтовая воспроизводимость PDF не заявляется: опубликованный бинарный файл имеет собственную контрольную сумму, а полнота текста сверяется с общим источником."
        },
        {
          "type": "heading",
          "id": "publication-patents",
          "text": "Учет патентных ограничений"
        },
        {
          "type": "p",
          "text": "Авторские права на материалы и патентные права регулируются отдельно. Пункт 9 условий использования устанавливает собственное обязательство лицензиара по необходимым контролируемым им патентным правам, отказу от притязаний и оформлению безвозмездной лицензии с требуемой регистрацией; он не объявляет регистрационные действия уже выполненными. Редактор ведет открытый реестр известных заявленных ограничений: номер патента или заявки, правообладатель, затрагиваемые пункты, территория, срок и источник сведений. Предложение изменения содержит раскрытие известных автору существенных ограничений либо указание, что такие сведения ему не известны. Отсутствие записи не доказывает отсутствие прав третьих лиц."
        },
        {
          "type": "p",
          "text": "Для включения решения, использование которого требует патентных прав участника, правообладатель представляет отдельное письменное обязательство предоставить любому реализатору необходимые права без лицензионных платежей и на одинаковых условиях. Публикуются перечень контролируемых прав, разрешенные действия, территория, срок, порядок заключения договора и предусмотренной законом регистрации. Такое обязательство действует только от имени подписавшего его правообладателя; участие в обсуждении не означает распоряжения чужими правами."
        },
        {
          "type": "p",
          "text": "При известном существенном ограничении без указанных условий решение не включается в обязательный профиль: выбирается независимый вариант либо расширение не получает статус совместимого. Сообщение о новом ограничении регистрируется с анализом влияния, условиями правообладателя и решением по редакции. Публичные контакты для предложений используются и для раскрытия ограничений. Обещание отсутствия чужих патентов не дается."
        },
        {
          "type": "heading",
          "id": "publication-governance",
          "text": "Открытость и порядок развития"
        },
        {
          "type": "p",
          "text": "Открытый протокол систем оповещения — открытая спецификация протокола для систем оповещения. Текущий криптографический профиль предназначен для российского рынка. Открытость означает общедоступность документа, свободную независимую реализацию, переработку и отсутствие обязательной маркировки изготовителем спецификации. Выбор российских алгоритмов сам по себе не подтверждает выполнение требований регуляторов или сертификацию СКЗИ."
        },
        {
          "type": "p",
          "text": "Предложения от любого участника принимаются через опубликованные контакты сайта gksotcom.ru с темой «Открытый протокол систем оповещения: изменение». Предложение указывает редакцию, пункт, проблему, предлагаемый текст, влияние на совместимость и воспроизводимый пример. Редактор присваивает номер OA-год-порядковый номер и публикует предложение, решение и обоснование в журнале изменений; контактные данные и секреты без согласия автора не публикуются. Отклонение требует технического или правового основания. Членство, закупка оборудования и договор о неразглашении для участия не требуются."
        },
        {
          "type": "p",
          "text": "Изменение нормативного контракта публикуется как предложение не менее чем на 30 календарных дней до следующего стабильного выпуска. Исправление опасной ошибки может выпускаться немедленно с описанием несовместимости. Редактор отвечает за выпуск и не объявляет свое решение голосованием независимого органа. Независимая ветвь разрешена лицензией; несовместимая ветвь получает собственный идентификатор профиля и не выдается за исходную редакцию."
        },
        {
          "type": "p",
          "text": "Редакция 0.20 устанавливает профиль open-alert-draft/0.20, речевой профиль rtp-alert/4 и пакет доверия open-alert-trust/2. Комплект идентифицируется редакцией и SHA-256 манифеста publication-0.20.json. Несовместимое изменение формата или семантики требует нового идентификатора wire-профиля; исправление оформления его не меняет. При проверке соответствия указывается манифест использованного комплекта."
        }
      ]
    },
    {
      "id": "architecture",
      "title": "2. Архитектура: АРМ, УУ и ОУ",
      "blocks": [
        {
          "type": "list",
          "ordered": true,
          "items": [
            "Короткие команды экономят канал связи. XML-сообщение занимает не более 1024 байт и передается своей фактической длиной, без дополнения до предела. Сирены и заранее загруженные сообщения работают без непрерывной передачи звука. Для конечного канала от 64 кбит/с предусмотрены отдельные бюджеты управления и сжатой речи; транспортные расходы учитываются сверх размера команды.",
            "Один порядок управления подходит для оборудования разных производителей: сирен, речевых усилителей и контроллеров. Общие команды, состояния и ошибки позволяют развивать систему поэтапно и выбирать оборудование по функциям, надежности и стоимости.",
            "Протокол учитывает ограниченные ресурсы контроллеров. Размеры сообщений, число ожидающих запросов и объемы очередей ограничены, а дополнительные возможности объявляются отдельно. Изготовитель реализует необходимые функции без обязанности включать все расширения.",
            "Защищенный режим использует российские криптографические алгоритмы. Отдельный секрет каждой пары «устройство — участник» ограничивает последствия утраты одного ключа. Настройка «Только защищенный канал» запрещает открытое управление; выбранный режим виден оператору.",
            "Оператор различает прием команды, подтвержденное начало работы и остановку оборудования. Результат сопровождается причиной, доступными физическими подтверждениями и сведениями об авариях. Остановка оператором не отменяет подтверждение уже состоявшегося звучания.",
            "Потеря ответа не превращает повтор команды в повторный запуск. После обрыва связи можно установить состояние исполнения и адресно остановить его. Поведение при потере управления определяется заранее и не зависит от догадок пульта о состоянии оборудования.",
            "Одноадресная передача служит основой связи с каждым устройством. Отдельный групповой речевой профиль позволяет передавать один поток многим получателям на общих участках сети, сохраняя индивидуальные права, подготовку и результаты. Это уменьшает нагрузку источника и общих каналов при массовом оповещении.",
            "Опросы, контроль линий и питания, подготовка сообщений, настройка и обслуживание используют общие правила. Машинные схемы, проверяемые примеры и контрольные векторы помогают находить расхождения до подключения оборудования.",
            "Назначение команд и подтверждений связано с задачами систем оповещения: проверка готовности, задействование, передача речи, прекращение работы и отчет по получателям. Разбор ГОСТ Р 42.3.05-2023 определяет границы сопряжения и позволяет включать протокол в проект без подмены требований обязательного интерфейса заявлением об общей совместимости."
          ]
        },
        {
          "type": "architecture"
        },
        {
          "type": "table",
          "headers": [
            "Граница",
            "Ответственность"
          ],
          "rows": [
            [
              "Внешние сервисы / межузловой обмен АРМ",
              "Предупреждения, сценарии, группы, маршруты, происхождение запуска, делегирование, полномочия оператора и итог по получателям."
            ],
            [
              "АРМ / Ethernet-часть УУ",
              "Защищенное соединение, команды, собственные запросы прибора, ответы, события, управление сеансом и наблюдаемость."
            ],
            [
              "Исполнитель УУ / драйвер ОУ",
              "Одинаковые предметные действия для разных производителей, привязка ресурсов и перевод наблюдений в общую модель."
            ],
            [
              "Взаимодействие УУ / ОУ",
              "Управляющие воздействия и наблюдения состояния оконечного оборудования. Способ взаимодействия выбирает изготовитель. Функциональные профили: ВАУ и Сирена."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Разделение сетевой и исполнительной частей является функциональным и не устанавливает число плат, микроконтроллеров или отдельных изделий. Физический и программный способ взаимодействия УУ с усилителем, сиреной или другим ОУ выбирает изготовитель. ДОПУСКАЕТСЯ последовательный интерфейс, иная цифровая шина, дискретные сигналы или взаимодействие внутри одного микроконтроллера. Наличие COM-порта, отдельной платы управления и отдельного байтового протокола УУ–ОУ НЕ ТРЕБУЕТСЯ. Независимо от выбранного способа сохраняются требования к управлению ресурсами, срокам, сопоставлению действия с результатом и подтверждению начала и выключения. Для работы АРМ знание внутреннего интерфейса изготовителя НЕ ТРЕБУЕТСЯ. Управление и передача звука описываются отдельно; наличие управляющего интерфейса не означает передачи звука по нему."
        },
        {
          "type": "p",
          "text": "Состояния контроллера, взаимодействия с ОУ и самого ОУ различаются в пределах объявленных возможностей наблюдения. Доступный по сети УУ может иметь недоступный исполнительный тракт. Встроенное исполнение не требует имитации отдельного порта или обмена кадрами; граница наблюдения и достоверность результата указываются в профиле оборудования. Замена драйвера, параметров внутреннего интерфейса или ОУ меняет bindingGeneration и каталог возможностей; во время активного исполнения такая замена запрещена."
        },
        {
          "type": "p",
          "text": "Каталог различает адресуемый выход и общие ресурсы: усилитель, декодер, источник звука, общую шину управления и переключатель входов. Для них объявляются группы конфликтов. Несколько линий не означают независимое воспроизведение на каждой; выбор отдельных линий ДОПУСКАЕТСЯ только при объявленной возможности управления ими."
        },
        {
          "type": "heading",
          "id": "architecture-principles",
          "text": "Основания ключевых правил"
        },
        {
          "type": "note",
          "text": "Справочные пояснения связывают правила протокола с устраняемой неоднозначностью. Они не вводят дополнительных полномочий или гарантий; нормативные условия, состояния и исключения определены в указанных разделах."
        },
        {
          "type": "table",
          "headers": [
            "Рассматриваемый вопрос",
            "Принятое правило и его основание",
            "Граница гарантии",
            "Раздел"
          ],
          "rows": [
            [
              "Кто вправе воздействовать на ОУ?",
              "Аутентификация, права участника, владение и происхождение запуска проверяются раздельно. Положение в иерархии и знание sessionId не заменяют полномочий; отмена порученного запуска не дает права прерывать самостоятельное местное оповещение.",
              "УУ проверяет непосредственный аутентифицированный АРМ. Проверка межузловой цепочки поручений выполняется на уровне АРМ.",
              {
                "text": "3. Владение",
                "href": "#ownership"
              }
            ],
            [
              "К какому исполнению относится команда?",
              "Контекст bootId/controlEpoch и номера команд отделяют исполнения и повторы. session.cancel адресует исходный session.start через startSeq, в том числе когда ответ с sessionId потерян. Старая отмена не переносится на новый запуск.",
              "Удаление подробного результата не разрешает повторное воздействие старым номером. resultExpired означает отсутствие доступной подробности, а не отсутствие исполнения.",
              {
                "text": "7. Доставка и отмена",
                "href": "#delivery"
              }
            ],
            [
              "Что делать при неизвестном исходе?",
              "Потерянный ответ допускает как неисполнение, так и уже начавшееся воздействие. Требуются сверка состояния и предусмотренное восстановление. Новый независимый session.start запрещен при занятости или неподтвержденном безопасном состоянии ресурса.",
              "Отправка session.stop не доказывает off. До подтверждения выключения неопределенность физического состояния сохраняется.",
              {
                "text": "6. Жизненный цикл",
                "href": "#lifecycle"
              }
            ],
            [
              "Что подтверждает ответ?",
              "Прием команды, начало исполнения, выдача сигнала и физическое наблюдение имеют разные доказательства. Результат сообщается вместе с доступным уровнем наблюдения; успешная сетевая доставка не подменяет физический результат.",
              "Связь с УУ не доказывает исправность ОУ. Сетевые квитанции и цифровая полнота аудио не подтверждают слышимость и понимание сообщения населением.",
              {
                "text": "13. Речь и результат",
                "href": "#media-acceptance"
              }
            ],
            [
              "Почему подключение не дает новое владение?",
              "principalId, instanceId, controlEpoch, attachSeq и bootId решают разные задачи. Resume восстанавливает тот же контекст; дублер ДОЛЖЕН предварительно исключить прежнего активного писателя.",
              "Общий ключ и успешный handshake не доказывают, что другая копия АРМ больше не управляет устройством.",
              {
                "text": "3. Переподключение",
                "href": "#ownership-resume"
              }
            ],
            [
              "Что гарантирует защита от повторов?",
              "Дедупликация сетевой команды предотвращает повторное решение управляющего ядра в заданном контексте. Для физического интерфейса отдельно учитываются идентификация команды, потерянный ответ и возможность наблюдать результат.",
              "Старое ОУ без корреляции команд не получает гарантию однократного физического воздействия от одной лишь дедупликации Ethernet. Общий режим записи не доказывает выбранное содержимое.",
              {
                "text": "8. Интерфейс ОУ",
                "href": "#ou-profiles"
              }
            ],
            [
              "Почему значение сопровождается качеством?",
              "Значение, качество, свежесть, источник и наличие точки различаются. Полнота снимка и непрерывность журнала проверяются отдельно; неполный снимок не очищает аварии.",
              "Полный набор диагностических данных может описывать неисправное оборудование. Полнота передачи и исправность являются независимыми признаками.",
              {
                "text": "11. Снимки и события",
                "href": "#diagnostics"
              }
            ]
          ]
        }
      ]
    },
    {
      "id": "ownership",
      "title": "3. Владение и право прерывания",
      "blocks": [
        {
          "type": "p",
          "text": "Прерывать действующее оповещение может текущий исполняющий узел с соответствующими правами оператора либо вышестоящий узел, который запустил именно это оповещение через нижестоящий. Высота в иерархии, приоритет и знание runId сами по себе такого права не дают."
        },
        {
          "type": "table",
          "headers": [
            "Ситуация",
            "Результат"
          ],
          "rows": [
            [
              "А запустил через Б; А отменяет свой запуск",
              "Б проверяет сохраненное делегирование и отменяет только ветви этого запуска."
            ],
            [
              "Б запустил самостоятельно; А выше в иерархии",
              "А не получает права остановить этот независимый запуск."
            ],
            [
              "Б как текущий узел останавливает исполняемый у него запуск",
              "Проверяются права оператора Б и точная ссылка на исполнение."
            ],
            [
              "Внешний сервис передает originNodeId или высокий приоритет",
              "Поля сервиса не становятся доверенными полномочиями."
            ],
            [
              "Резервная копия АРМ подключается с общим ключом",
              "Общий ключ не доказывает право активной копии. Нужен механизм исключения двух владельцев."
            ]
          ]
        },
        {
          "type": "p",
          "text": "АРМ сохраняет originNodeId, runId, branchId и путь делегирования path из node-alert/2; идентичность каждой ветви задается тройкой originNodeId/runId/branchId, отдельного delegationId нет. Прием поручения, изменение ветви, исходящая доставка и аудит связываются локальной транзакцией. Отмена, пришедшая до запуска, оставляет запрет позднего запуска до истечения срока поручения с запасом на часы."
        },
        {
          "type": "p",
          "text": "УУ в базовом профиле доверяет аутентифицированному непосредственному владельцу и проверяет конкретный сеанс. Проверка полной межузловой цепочки на приборе и сквозная подпись поручения являются отдельным расширением. Защита Ethernet-канала не защищает от захваченного доверенного АРМ."
        },
        {
          "type": "p",
          "text": "Допуск АРМ к устройству и владение конкретным исполнением проверяются раздельно. Несколько АРМ могут иметь право управления одним УУ, но это не дает каждому права отменять чужой сеанс. Чтение состояния, обнаружение прибора и наличие его карточки в АРМ сами по себе не предоставляют владение."
        },
        {
          "type": "p",
          "text": "IP-адрес определяет сетевой маршрут, а не владельца. Смена адреса того же участника требует подтверждения идентичности и control.resume с прежним контекстом; новый адрес не обнуляет историю. Передача управления другому участнику ДОПУСКАЕТСЯ только по правилам профиля после завершения исполнения и подтверждения допустимого состояния ресурсов. Неизвестное выключение не считается свободным устройством. Перезапуск АРМ без сохраненного контекста не дает права присвоить продолжающийся сеанс."
        },
        {
          "type": "p",
          "text": "Передача права говорить, квитирование аварии, чтение состояния и администрирование не передают владение оповещением. Сброс, перезагрузка, отзыв медиапотока и изменение маршрута не могут использоваться для обхода права прерывания. Встроенная аппаратная защита от аварии действует по профилю ОУ и регистрируется с собственной причиной; она не выдается за операторскую отмену."
        },
        {
          "type": "heading",
          "id": "ownership-states",
          "text": "Состояния владения"
        },
        {
          "type": "table",
          "headers": [
            "Состояние владения",
            "Переход"
          ],
          "rows": [
            [
              "free",
              "control.acquire выдает одну эпоху и прикрепляет канал при допустимом состоянии ресурсов."
            ],
            [
              "held",
              "Один principalId и instanceId, одна эпоха и один прикрепленный канал. Опрос наблюдателя владение не получает и не продлевает."
            ],
            [
              "detached",
              "Канал потерян, эпоха и история сохранены. Тот же экземпляр восстанавливается через resume. До истечения lease другой участник получает busy при наличии исполнения; свободное владение уступает real по правилу обслуживания ниже."
            ],
            [
              "draining",
              "Lease истек во время исполнения: применяется stop/finish. Новый session.start запрещен, эпоха сохраняется для адресной отмены и чтения результата."
            ],
            [
              "blocked",
              "Выключение неизвестно. Диагностика и адресная остановка своего известного исполнения разрешены; новый владелец и session.start запрещены."
            ],
            [
              "released",
              "Явное освобождение, истечение lease/idleDeadlineMs, конец технического теста либо передача свободного владения real при подтвержденном off. Старый acquire не выдает управление повторно."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "ownership-acquire",
          "text": "Выдача управления и повторы"
        },
        {
          "type": "p",
          "text": "acquireSeq - uint64 > 0, монотонный номер попытки в области principalId/deviceId/bootId. АРМ ДОЛЖЕН сохранить его и instanceId до отправки. УУ хранит для каждого допущенного principalId старший acquireSeq, нормализованный запрос и его последнее решение до смены bootId. Повтор того же номера и содержимого возвращает ту же controlEpoch; изменение instanceId или purpose дает conflict; меньший номер дает resultExpired. Новый больший номер расходуется и при busy. Отказ аутентификации, старый bootId и неверная оболочка номер не расходуют. Повтор потерянного busy остается busy; новая попытка требует нового номера."
        },
        {
          "type": "p",
          "text": "Ответ acquire содержит current: существует ли выданное владение сейчас. Повтор после освобождения возвращает прежнее решение с current=false и не выдает эпоху заново. Повтор acquire с другого соединения только читает решение: для прикрепления нужен resume. Тем самым потеря ответа acquire не требует угадывать эпоху или останавливать ОУ."
        },
        {
          "type": "p",
          "text": "В повторе acquire неизменны решение о выдаче, acquireSeq, instanceId, purpose и назначенная эпоха. current, highWater, attachSeq и leaseRemainingMs отражают состояние на момент ответа. Восстановление связи продлевает lease только после успешного resume; оно не отменяет уже принятую остановку и не возвращает stopping в running."
        },
        {
          "type": "heading",
          "id": "ownership-resume",
          "text": "Переподключение и дублер"
        },
        {
          "type": "p",
          "text": "instanceId идентифицирует единственный активный экземпляр АРМ и сохраняется с контекстом. resume несет возрастающий attachSeq. После отсоединения прежнего канала УУ атомарно сохраняет новый attachSeq и прикрепляет новый канал. При живой прежней привязке другой канал получает busy/detail=ownershipHeld. Точный повтор на том же канале идемпотентен; равный номер с другого канала получает staleAttachment, меньший номер также отклоняется. Потеряв ответ, экземпляр использует следующий сохраненный attachSeq. Изменение IP не меняет instanceId, эпоху или историю."
        },
        {
          "type": "p",
          "text": "control.release обязателен в ядре. После него повтор той же команды на исходном либо новом допущенном канале той же идентичности по правилам раздела 7 получает сохраненный ответ, пока запись входит в кеш завершенной эпохи; он не восстанавливает владение. Проверка точного повтора release выполняется до отказа по released, но после проверки principalId и bootId. При вытеснении записи возвращается resultExpired. Новая выдача управления не принимает изменяющие команды прежней эпохи."
        },
        {
          "type": "p",
          "text": "Базовый профиль использует одного активного писателя для instanceId. Для резервирования ТРЕБУЕТСЯ внешнее ограждение прежней копии: ее питание либо все пути управления к УУ блокируются независимым от нее средством и подтверждаются до передачи контекста резерву. Ограждение сохраняется до возврата узла под контроль координатора. Отсутствие доказанного ограждения запрещает автоматический resume резервом. Один лишь общий ключ, attachSeq или решение самой копии о своей активности не являются доказательством. Такая схема сохраняет происхождение запуска и не передает чужое оповещение другому principalId."
        },
        {
          "type": "heading",
          "id": "ownership-loss",
          "text": "Потеря связи и таймеры"
        },
        {
          "type": "p",
          "text": "Потеря транспорта и истечение lease - разные события. Закрытие прикрепленного канала само по себе не сокращает и не продлевает уже выданный lease. Политика onControlLoss применяется при его истечении; немедленное аннулирование действующих полномочий по разделу 12 является самостоятельным событием. Отказ ОУ и собственные сроки исполнения обрабатываются независимо. Состояния владения ниже не заменяют состояния сеанса starting/running/stopping/finished."
        },
        {
          "type": "table",
          "headers": [
            "Исходное владение",
            "Событие",
            "Действие",
            "Новое владение",
            "Судьба таймеров"
          ],
          "rows": [
            [
              "free / released",
              "Успешный новый acquire",
              "Выдать эпоху и прикрепить канал.",
              "held",
              "Lease начинается при выдаче управления; таймер сеанса еще не запущен."
            ],
            [
              "held",
              "Закрытие прикрепленного соединения, включая таймаут сборки или отправки",
              "Отсоединить канал, сохранить эпоху и принятое исполнение.",
              "detached",
              "Lease идет от последнего продления; все сроки сеанса продолжаются."
            ],
            [
              "held",
              "Разрешенный запрос прикрепленного владельца до истечения lease",
              "Обработать запрос по его контракту; lease продлевается только для команд, которым это разрешено. Отклоненный запрос, TCP ACK и запрос наблюдателя не продлевают lease.",
              "held",
              "Предусмотренное контрактом продление начинает lease заново. Сроки сеанса неизменны; idleDeadlineMs меняется только при явно описанных переходах, включая проверку полного файла по разделу 14."
            ],
            [
              "detached",
              "Успешный resume до истечения lease",
              "Атомарно прикрепить новый канал. Уже начатая остановка продолжается.",
              "held",
              "Lease начинается заново; prepareTimeoutMs, durationMs, maxDurationMs и сроки остановки не сбрасываются."
            ],
            [
              "held / detached",
              "Lease истек; есть конечное исполнение",
              "Применить сохраненную stop/finish. При stop начать stopping; при finish продолжить принятое исполнение. Если stopping уже начат, продолжить его.",
              "draining",
              "Lease больше не разрешает session.start. Сроки исполнения не продлеваются; сроки остановки начинаются только при первом переходе в stopping."
            ],
            [
              "held / detached",
              "Lease истек; исполнения нет и off подтвержден",
              "Освободить владение; сохранить доступный результат и историю по их срокам.",
              "released",
              "Lease прекращается; срок хранения результата не меняется."
            ],
            [
              "held / detached",
              "Применен отзыв полномочий, на которых основано текущее владение",
              "Немедленно аннулировать право управления и применить сохраненную stop/finish. При активном исполнении сохранить его происхождение; без исполнения освободить владение только при подтвержденном off.",
              "draining при исполнении; released при подтвержденном off; иначе blocked",
              "Lease больше не разрешает команды независимо от остатка. Уже идущие сроки исполнения/остановки не сбрасываются. Отозванные полномочия не допускают resume."
            ],
            [
              "draining",
              "Транспорт восстановлен; успешный resume прежнего владельца",
              "Прикрепить канал для адресной отмены и чтения. Запрет нового session.start сохраняется, finish не становится новым запуском.",
              "draining",
              "Resume не отменяет факт потери управления и не сбрасывает сроки исполнения/остановки."
            ],
            [
              "blocked",
              "Транспорт восстановлен; успешный resume прежнего владельца с достоверным контекстом",
              "Разрешить диагностику и адресную остановку своего исполнения. Само подключение не подтверждает off.",
              "blocked",
              "Сроки не перезапускаются; снятие блокировки требует процедуры восстановления."
            ],
            [
              "draining / blocked",
              "Исполнение завершено, off подтвержден, результат надежно сохранен и восстановление завершено",
              "Освободить эпоху. Следующее управление требует нового acquire.",
              "released",
              "Сохранение результата продолжается независимо от освобождения эпохи."
            ],
            [
              "held",
              "Обычный сеанс завершен при действующем lease и подтвержденном off",
              "Сохранить результат; право владельца остается до lease или idleDeadlineMs.",
              "held",
              "idleDeadlineMs=finishedAtMs+10000; lease не продлевается. Технический сеанс после сохранения результата освобождает эпоху сразу."
            ],
            [
              "Любое",
              "Выключение неизвестно либо не удалось надежно закрыть запись воздействия",
              "Запретить новый session.start и выдачу управления; сохранить происхождение и выполнять восстановление.",
              "blocked",
              "Таймаут и повторное подключение не снимают блокировку."
            ]
          ]
        },
        {
          "type": "p",
          "text": "При now >= срока lease сначала применяется его истечение, затем обрабатывается поступивший запрос: запоздалый ping/resume не возвращает draining в held. В draining/blocked успешный resume может обновить привязку и срок связи, но этот срок не дает права нового session.start. Закрытие уже отозванного канала или канала наблюдателя не меняет владение. Само открытие TCP/TLS без успешного resume не прикрепляет владельца. После released прежний resume получает staleEpoch; повтор acquire только читает прежнее решение с current=false."
        },
        {
          "type": "table",
          "headers": [
            "Трасса передачи владения",
            "Входные условия",
            "Обязательный исход"
          ],
          "rows": [
            [
              "service-handover",
              "Свободная эпоха maintenance, идет загрузка; разрешенный acquire real получен при t=0.",
              "Шаг обслуживания завершен до 100 мс, решение и ответ новой выдачи до 500 мс; старая эпоха не фиксирует очереди; подтвержденные байты сохранены."
            ],
            [
              "idle-heartbeat",
              "Свободная эпоха real выдана при t=0; heartbeat каждую 1000 мс, START отсутствует.",
              "Освобождение не позднее t=10000 мс; heartbeat не продлевает idleDeadlineMs."
            ],
            [
              "active-owner",
              "Действующее или неопределенное чужое исполнение и acquire real другого участника.",
              "busy; действующее чужое исполнение не прерывается."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "ownership-service-handover",
          "text": "Свободное владение, обслуживание и реальная тревога"
        },
        {
          "type": "p",
          "text": "control.acquire содержит обязательный control.purpose=real|exercise|technical|maintenance. Назначение сохраняется в эпохе и ответах acquire/resume; session.start.purpose ДОЛЖНО совпадать с ним. maintenance не разрешает session.start. Назначение real требует отдельного права реального оповещения, а не выводится из административной роли. До первого session.start свободная эпоха живет не более 10000 мс от acquire; после завершения обычного сеанса - не более 10000 мс от finished. Проверка связи, чтение и повторы этот idleDeadlineMs не продлевают. Для maintenance действуют только два исключения раздела 14: новый устойчивый префикс bulk/1 освежает idleDeadlineMs, а выполняющаяся validating/verifying использует фиксированный срок проверки до 60000 мс. Оба исключения сохраняют независимый lease и отзыв в пользу real. Истечение освобождает только эпоху без активного/неопределенного исполнения при подтвержденном off. Сеансы technical и exercise освобождают эпоху сразу после off и фиксации результата."
        },
        {
          "type": "p",
          "text": "При control.acquire purpose=real другого допущенного участника и отсутствии действующего/неопределенного сеанса УУ отзывает свободную эпоху exercise/technical/maintenance на ближайшей границе сервисного шага. Шаг не превышает 100 мс; решение о выдаче новой эпохи и ответ укладываются в ordinaryResponseMs=500 мс от полного запроса. До ответа новый владелец не действует. Сначала аннулируются полномочия старого соединения, затем публикуется новый controlEpoch; старые очереди повторно проверяют эпоху перед фиксацией любого изменения. Подтвержденный блок загрузки сохраняется, неподтвержденный не считается принятым. Отзыв не запускает rollback действующей конфигурации и не стирает подготовленный файл."
        },
        {
          "type": "p",
          "text": "Действующее оповещение, в том числе технический сеанс, этим правилом не прерывается. При busy для разрешенного real УУ фиксирует очередь намерений по principalId до 35000 мс от первой попытки. Повторы и новые acquireSeq того же участника не сдвигают срок и место; чужой instanceId не наследует место. Порядок FIFO, при равном времени - лексикографический principalId. Новая выдача свободного владения ДОПУСКАЕТСЯ только первому живому намерению; maintenance/exercise/technical не опережают его. Клиент real повторяет acquire с новым acquireSeq через 500..2000 мс после получения busy, пока его срок актуален; следующий запрос не отправляется до ответа на предыдущий. В alarmTransitionMaxMs входят ожидание этого интервала, завершение уже отправленного запроса, доставка и обработка следующей попытки; для допуска теста используется их общая верхняя оценка по разделу 10. Возвращенный busy не превращается в выдачу самопроизвольно. При отсутствии нового полного допустимого acquire в течение 10000 мс намерение удаляется; исходный срок очереди 35000 мс не продлевается. Потеря соединения и смена bootId удаляют намерение. Число записей ограничено числом заранее допущенных участников. Уже выданная real-эпоха сохраняется до собственного idleDeadlineMs или завершения сеанса."
        },
        {
          "type": "p",
          "text": "Граница доступности: отзыв свободного сервисного владения занимает не более 500 мс локально; доставка запроса/ответа и следующего session.start добавляет бюджет сети. При занятой свободной real-эпохе добавляется не более оставшихся 10000 мс idleDeadlineMs и период повторной попытки. Неисправность хранилища, неизвестное выключение и активное чужое исполнение не обходятся приоритетом. Операции, выводящие сам контроллер из готовности (firmware.commit и network.stage), разрешены только при местном разрешении обслуживания и готовом независимом тракте тревоги, указанном в конфигурации развертывания. Разрешение и готовность подтверждаются независимыми входами, не устанавливаются сетевой сервисной командой; без них unsafeState. На время перезагрузки/смены сети тревога обслуживается резервным трактом."
        },
        {
          "type": "heading",
          "id": "ownership-exercise-budget",
          "text": "Допуск учений и доступность реального оповещения"
        },
        {
          "type": "p",
          "text": "Техническая проверка mode=test либо purpose=technical имеет maxDurationMs не более 30000. Полноценные учения purpose=exercise допускают сирену, запись, программу или поток общей длительностью до 1080000 мс, включая подготовку и паузы. Учения всегда применяют onControlLoss=stop, stopBeginMs не более 100 и stopConfirmMs не более 3000; отсутствие таких физических возможностей дает unsafeState до воздействия. После off и сохранения результата эпоха автоматически освобождается. Следующий учебный запуск требует нового acquire и не обходит ожидающую тревогу."
        },
        {
          "type": "p",
          "text": "Сохранение результата и освобождение эпохи после off занимают не более 500 мс. Для технической проверки и учения до 30000 мс применяется единый бюджет раздела 10: 30000+3000+2000=35000 мс до принятия нового session.start первого ожидающего real. Последние 2000 мс включают сохранение результата, освобождение, ожидание повтора, сетевой обмен и обработку acquire и session.start. Предел сетевой доставки 1500 мс сам по себе не обеспечивает этот срок. Если сумма выбранных пределов больше, допустимая длительность теста или короткого учения уменьшается. Длительное учение допускается только при независимом готовом тракте реального оповещения, не занятом учением, с alarmTransitionMaxMs не более 35000 мс. При отсутствии такого тракта длительность exercise ограничивается 30000 мс с соблюдением общего бюджета. Самостоятельная тревога не отбирает чужую эпоху; независимость ресурсов и путей управления проверяется до допуска длительного учения. Если проект требует меньшего срока, применяется именно он. Неподтвержденный off и отказ Flash обрабатываются предусмотренным резервом."
        },
        {
          "type": "p",
          "text": "Живая привязка означает одновременно открытое допущенное соединение выбранного режима (аутентифицированное при tls) и now < leaseDeadlineMs. Lease длится 30000 мс от последнего допустимого продления; heartbeat или другой разрешенный запрос прикрепленного владельца продлевает его по общим правилам. TCP ACK и запрос наблюдателя не продлевают. Обнаруженное закрытие TLS/TCP либо истечение таймера сборки/отправки немедленно отсоединяет канал: resume разрешен без ожидания остатка lease, но только прежней идентичности и после требуемого ограждения резервной копии. Необнаруженный обрыв остается живой привязкой до указанного предела; УУ не угадывает доступность удаленной копии."
        },
        {
          "type": "p",
          "text": "Плановая смена активной копии при подтвержденном off: прежняя копия выполняет control.release и прекращает управление, затем новая выполняет control.acquire с собственным сохраненным контекстом. После release resume завершенной эпохи запрещен. Передача активного сеанса без новой эпохи: координатор независимо ограждает прежнюю копию, УУ отсоединяет ее канал, резерв с сохраненным разрешенным контекстом выполняет resume; уже начатая остановка не отменяется. Если ограждение не доказано, такой переход запрещен. Для кластера с несколькими УУ отдельные привязки не выбирают единую активную копию: разные копии могут занять разные УУ. Координатор ДОЛЖЕН обеспечить общее независимое ограждение для всего набора УУ до передачи полномочий."
        },
        {
          "type": "p",
          "text": "Прикрепление является ограждением на стороне УУ: каждая изменяющая команда, heartbeat и отложенная сервисная операция повторно сверяют внутренний идентификатор соединения с текущей привязкой непосредственно перед изменением состояния. Старый канал не получает полномочий от знания controlEpoch или большого cmdSeq. Resume с другого канала ДОПУСКАЕТСЯ только после отсоединения прежнего. Пока прежний канал прикреплен и его lease не истек, возвращается busy/detail=ownershipHeld без изменения attachSeq, highWater, привязки и lease. Точный повтор resume на уже прикрепленном канале сохраняет идемпотентность. Истечение lease сначала применяет политику потери управления и отсоединяет старый канал; это не разрешает новый session.start в draining."
        },
        {
          "type": "p",
          "text": "Привязка не выбирает лидера среди копий с одинаковыми секретами. Автоматическая передача сохраненного instanceId резерву ДОПУСКАЕТСЯ только после независимого ограждения прежней копии, установленного разделом 3. До этого резерв может наблюдать, но НЕ ДОЛЖЕН выполнять acquire, resume, session.start или продлевать lease от имени активной копии. Увеличиваемое самой копией число, в том числе attachSeq, не является разрешением арбитра. Для реализации с программным арбитром его проверяемое разрешение и отдельная идентичность каждой копии требуют отдельного согласованного профиля; арбитраж между несколькими УУ не входит в базовый профиль."
        }
      ]
    },
    {
      "id": "identity",
      "title": "4. Идентификаторы и контекст",
      "blocks": [
        {
          "type": "table",
          "headers": [
            "Поле",
            "Тип и смысл"
          ],
          "rows": [
            [
              "deviceId / bootId",
              "128 бит, 32 строчных hex-символа. Идентичность устройства / текущего запуска прошивки. bootId меняется при перезагрузке. IP не является deviceId."
            ],
            [
              "channelId / controlEpoch",
              "Отдельные 128-битные ID защищенного подключения и периода владения. Переподключение не сбрасывает историю команд текущего владения."
            ],
            [
              "capabilityRevision",
              "Ревизия возможностей и состава каталога точек. Каталог и значения связываются через нее; изменение состава или смысла точки меняет ревизию."
            ],
            [
              "id / to",
              "128-битный ID запроса и ссылка ответа на запрос. Направление обмена входит в область корреляции."
            ],
            [
              "cmdSeq / startSeq",
              "uint64 в десятичной записи. cmdSeq растет в bootId/controlEpoch; startSeq равен cmdSeq исходного session.start."
            ],
            [
              "sessionId / operationId",
              "128-битные ID принятого сеанса и длительной операции. Идентификатор сеанса назначает УУ."
            ],
            [
              "ouId / bindingGeneration",
              "Локальная привязка ОУ и ее поколение. Это контекст драйвера и наблюдений, а не замена криптографической идентичности."
            ],
            [
              "stateRevision / eventSeq",
              "Версия согласованного состояния и курсор событий в пределах bootId. Значения не сравниваются между загрузками."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Счетчики uint64 передаются строкой десятичных цифр, без знака, экспоненты и дробной части. В JavaScript используется BigInt или строка, а не Number. При исчерпании счетчика новые действия в его области запрещены; перенос через ноль недопустим. Завершение уже принятого исполнения и чтение результата сохраняются. Новая область создается только штатной сменой контекста после подтвержденного выключения; скрытая перезагрузка ради сброса счетчика запрещена."
        },
        {
          "type": "p",
          "text": "controlEpoch является непрозрачным идентификатором, а не упорядоченным fencing-счетчиком. УУ Обязательно принимать изменяющую команду только при точном совпадении bootId, текущего controlEpoch, владельца и действующего канала. Несовпавшая эпоха отклоняется независимо от лексикографического значения. При control.resume привязка нового канала и отзыв прежнего выполняются атомарно до разрешения следующего изменения; уже переданные физические действия учитываются при согласовании состояния. Право резервной копии выполнить resume проверяется по правилу ограждения раздела 3, отдельно от знания эпохи."
        },
        {
          "type": "p",
          "text": "Группа фиксируется в плане АРМ до запуска. Добавление прибора в группу не включает его в уже начатую ветвь. Два маршрута к одному УУ требуют дедупликации на уровне координатора."
        },
        {
          "type": "p",
          "text": "Назначение запуска (реальное оповещение, учения или техническая проверка) задается отдельно от физического режима siren/recording/program/stream/test. Учебное оповещение может звучать. Предварительная оценка readiness не имеет воздействия. Назначение передается полем purpose=real|exercise|technical; право каждого назначения хранится отдельно; подмена звучащих учений тихой проверкой или обратная подмена запрещены."
        },
        {
          "type": "heading",
          "id": "identity-normative",
          "text": "Резерв номеров для остановки и освобождения"
        },
        {
          "type": "p",
          "text": "Для cmdSeq установлены три области: обычные номера 1..18446744073709551613, номер завершения 18446744073709551614 и номер освобождения 18446744073709551615. Обычный новый номер превышает highWater не более чем на 4096; пропуски в этом окне допустимы. Последние два номера НЕ ДОЛЖНЫ использоваться для session.start, изменения конфигурации, неизвестного действия или другого обычного изменения. Проверка области номера относится к общей оболочке и предшествует расходованию highWater."
        },
        {
          "type": "table",
          "headers": [
            "Номер",
            "Допустимое действие",
            "Условие расходования"
          ],
          "rows": [
            [
              "1..18446744073709551613",
              "Любое разрешенное изменяющее действие",
              "Новый номер в highWater+1..min(highWater+4096,18446744073709551613); предметные отказы сохраняются по общему правилу."
            ],
            [
              "18446744073709551614",
              "Только session.stop или session.cancel",
              "Все поля и полномочия проверены. При незавершенном или невыключенном собственном исполнении ссылка ДОЛЖНА совпадать с ним. При отсутствии такого исполнения session.cancel ставит запрет позднего session.start, а session.stop ДОПУСКАЕТСЯ только для известного завершенного сеанса."
            ],
            [
              "18446744073709551615",
              "Только control.release",
              "Нет действующего или неопределенного исполнения, off подтвержден, результат и закрытие журнала сохранены. Расходование номера и освобождение эпохи атомарны."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Любой отказ проверки зарезервированного номера, включая неверное тело, чужой startSeq/sessionId, неизвестный сеанс и преждевременный control.release, НЕ ДОЛЖЕН изменять highWater, занимать запись дедупликации или продлевать lease. Возвращается invalidArgument для области/формы, forbidden для полномочий, notFound для неизвестного session.stop, conflict с detail=operationStateMismatch для чужого исполнения либо unsafeState для преждевременного control.release. Это специальное правило имеет приоритет перед обычным расходованием номера при предметном отказе."
        },
        {
          "type": "p",
          "text": "После расходования номера завершения новые обычные действия запрещены. Точный повтор session.stop/session.cancel с тем же номером и нормализованным телом сохраняет смысл и доступен до освобождения эпохи; его решение не вытесняется. Повтор не начинает воздействие заново и не продлевает сроки остановки. Исполнитель продолжает ранее принятую остановку и предусмотренное восстановление. При подтвержденном off применяется номер освобождения; следующее владение получается новым acquireSeq. Нумерация никогда не переносится через ноль."
        },
        {
          "type": "p",
          "text": "АРМ ДОЛЖЕН резервировать номер завершения независимо от результата обычной команды. При unknown исходного session.start он использует session.cancel с исходным startSeq; при известном sessionId - session.stop. Одновременные обычные изменения после выдачи отмены запрещены до решения этой гонки. УУ ДОЛЖЕН сохранять возможность принять адресную отмену своего исполнения при highWater в обычной области; значение highWater=18446744073709551615 допустимо только у освобожденной эпохи."
        },
        {
          "type": "p",
          "text": "cmdSeqWindow - обязательное имя limit в device.capabilities, unit=count, value=4096. Клиент хранит номера до передачи и сверяет highWater при восстановлении. Протокол не требует скачков выше окна: операции выдаются последовательно с отдельным резервом отмены. Смена процесса, восстановление резервной копии и откат локального журнала не разрешают произвольный скачок вверх. При утраченном контексте старой эпохи АРМ выясняет исход через чтение, при необходимости прекращает собственное исполнение и получает новую эпоху после безопасного освобождения. Номер ниже highWater не становится новым действием; доступен только сохраненный точный повтор либо resultExpired."
        },
        {
          "type": "p",
          "text": "Окно обычного cmdSeq равно 4096. Для нового обычного номера обязательны cmdSeq > highWater и cmdSeq - highWater <= 4096; вычитание выполняется только после сравнения, без переполнения. При исходном highWater=0 первый номер находится в 1..4096. Известный точный повтор проверяется до окна. Превышение окна дает invalidArgument до расходования номера, изменения состояния, дедупликации и продления lease. Зарезервированные номера адресной остановки и освобождения исключены из окна; их условия раздела 4 сохраняются. После исчерпания обычной области АРМ останавливает свое исполнение, освобождает эпоху и получает новую; счетчик не переносится через ноль."
        }
      ]
    },
    {
      "id": "transport",
      "title": "5. Транспорт и ограниченный XML",
      "blocks": [
        {
          "type": "p",
          "text": "Прикладная модель отделена от транспорта: команды описывают владение, конечное исполнение, отмену и физическое наблюдение. Ограниченный XML выбран для небольших сообщений контроллера, строгого разбора и чтения трасс при наладке. Лимиты являются условиями профиля; их реализуемость на каждом изделии подтверждается отдельно. NETCONF и RESTCONF задают прежде всего управление данными; MQTT задает доставку сообщений; их применение само по себе не определяет эти правила оповещения. Адаптеры таких систем располагаются на границе АРМ и сохраняют предметную семантику."
        },
        {
          "type": "p",
          "text": "Используется постоянное TCP-соединение выбранного режима tls либо plain для управления и отдельный канал медиаданных. Прикладное обрамление после проверки защиты: xmlLength:uint32 BE, затем ровно xmlLength байтов UTF-8. TCP-сегмент не является сообщением. Часть префикса, часть тела и несколько сообщений в одном чтении обрабатываются корректно."
        },
        {
          "type": "table",
          "headers": [
            "Ограничение профиля",
            "Значение"
          ],
          "rows": [
            [
              "Размер XML / глубина / число элементов",
              "1024 байта / 4 с учетом корня / 32"
            ],
            [
              "Атрибутов на элемент / длина имени",
              "12 / 32 ASCII-байта"
            ],
            [
              "Декодированное строковое значение",
              "До 128 байт UTF-8"
            ],
            [
              "Сборка неполного сообщения",
              "10000 мс от первого байта кадра, включая префикс длины, без продления каждым байтом"
            ],
            [
              "Запросы в полете в проектном профиле",
              "1 обычный запрос, 1 слот session.stop/session.cancel и 1 слот channel.ping; по одному запросу без ответа на слот"
            ],
            [
              "Большие ответы",
              "Страницы с ограничением по байтам; недостаточно ограничивать только число строк"
            ]
          ]
        },
        {
          "type": "list",
          "items": [
            "Корни сообщений: request и response. event является записью внутри ответа events.read. v=\"1\" задает версию оболочки, profile=\"open-alert-draft/0.20\" - точную редакцию проектной схемы. Пространства имен в базовом профиле отсутствуют.",
            "DTD, пользовательские сущности, внешние ресурсы, CDATA, комментарии, processing instructions, XML-декларация и BOM запрещены.",
            "Пять стандартных XML-сущностей и числовые ссылки допускаются после проверки символов и размера декодированного значения.",
            "Повтор атрибута, неверный тип, неизвестное управляющее поле или лишний элемент-одиночка отклоняют весь запрос. Порядок атрибутов и вид кавычек не меняют смысл.",
            "Неверная длина или синтаксис XML закрывают соединение. Корректный XML с ошибкой команды получает структурированный отказ. Парсер не включает выходы по мере чтения."
          ]
        },
        {
          "type": "note",
          "text": "XML в примерах показывает прикладное тело. При tls оно передается внутри защищенного потока, при явно выбранном plain — непосредственно по TCP. Настройка securityPolicy УУ определяет допустимость открытого режима; порт и направление задаются при вводе."
        },
        {
          "type": "heading",
          "id": "transport-bounds",
          "text": "Обрамление и приоритет отмены"
        },
        {
          "type": "p",
          "text": "Пределы этой таблицы относятся к сообщениям АРМ-УУ после расшифрования. АРМ-АРМ, пакет конфигурации и внешнее предупреждение имеют собственные профили и бюджеты; целый CAP-документ не помещается в команду УУ автоматически. Страница является самостоятельным ограниченным ответом; следующая страница запрашивается отдельно."
        },
        {
          "type": "p",
          "text": "УУ и АРМ ДОЛЖНЫ обслуживать дополнительный слот session.stop/session.cancel независимо от ожидания обычного ответа. Ответы могут идти в другом порядке и связываются через to. Отмена имеет приоритет перед следующей страницей, сервисным блоком и новым запуском. Уже передаваемый кадр TCP не прерывается: его максимальный размер и время передачи входят в бюджет остановки. УУ ДОЛЖЕН ограничивать исходящую очередь и неблокирующе принимать входящие команды; медленное чтение клиентом не блокирует исполнитель. Истечение таймера передачи закрывает канал и вызывает переход по разделу 3: прикрепленный владелец переходит в detached, оставшийся lease продолжает действовать. Сам таймаут не сокращает lease и не применяет onControlLoss раньше его истечения; отдельное событие отзыва полномочий определено в разделе 12. Закрытие канала не подтверждает исполнение session.stop."
        },
        {
          "type": "heading",
          "id": "transport-timers",
          "text": "Таймеры и бюджеты"
        },
        {
          "type": "table",
          "headers": [
            "Таймер/бюджет ядра",
            "Нормативное значение и начало отсчета"
          ],
          "rows": [
            [
              "heartbeatIntervalMs / controlLeaseMs",
              "5000 / 30000 мс. Lease продлевает только принятый запрос прикрепленного владельца; TCP ACK и запрос наблюдателя его не продлевают."
            ],
            [
              "ordinaryResponseMs",
              "До 500 мс от полного проверенного кадра до постановки ответа на отправку. Длительная операция возвращает accepted и идентификатор; ожидание физического результата не удерживает обычный слот."
            ],
            [
              "cancelAdmissionMs",
              "До 100 мс от полного проверенного session.stop/session.cancel до решения исполнителя. Это локальная задержка, без времени сетевой доставки."
            ],
            [
              "frameAssemblyMs / writeProgressMs",
              "10000 / 10000 мс. Первый таймер начинается с первого байта кадра. Второй начинается при готовности ответа, включает ожидание в очереди и передачу целого кадра защищенному транспорту; буфер TLS и фактическая отправка также ДОЛЖНЫ укладываться в этот бюджет. Продвижение отдельных байтов срок не продлевает."
            ],
            [
              "prepareTimeoutMs",
              "От acceptedAtMs принятого session.start до выполнения критерия running для всех выбранных ресурсов. Включает подготовку, ожидание драйвера и подтверждений ОУ; доставка ответа accepted АРМ не является началом. Истечение в starting инициирует остановку с причиной prepareTimeout."
            ],
            [
              "durationMs (siren)",
              "От перехода сеанса в running; включает все паузы intermittent. Время starting не входит в durationMs, но входит в maxDurationMs. Истечение инициирует stopping без ожидания конца цикла сигнала."
            ],
            [
              "maxDurationMs",
              "От acceptedAtMs, включая starting и паузы. При истечении во время starting/running инициируется stopping с причиной maxDurationExceeded. Это предел до начала остановки; подтверждение off имеет отдельный бюджет."
            ],
            [
              "stopBeginMs / stopConfirmMs",
              "Обязательные значения паспорта ОУ: 1..1000 и 1..10000 мс, stopBeginMs <= stopConfirmMs; для technical не выше 100/3000 мс. Сроки идут от решения stopping до выдачи физической команды и до подтверждения выключения. Истечение второго дает stopUnconfirmed."
            ],
            [
              "recoveryTimeoutMs",
              "От входа в reconciling после загрузки или обнаруженного отказа. Возврат сети, опрос и повтор команды не начинают срок заново; истечение требует localInterventionRequired."
            ],
            [
              "resultRetentionMs",
              "От перехода в finished в текущем bootId; новый снимок или reconnect срок не перезапускает. Граница гарантии после reboot определена в разделе 6."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Все сроки считаются по монотонным часам УУ. acceptedAtMs фиксируется однократно при принятии session.start в сохраняемом намерении; затраты его фиксации не дают права перенести срок вперед. УУ проверяет истекшие сроки перед новым физическим действием. При now >= срока таймер истек: позднее подтверждение не дает заново включить ОУ. Если одновременно истекли prepareTimeoutMs и maxDurationMs, применяется maxDurationExceeded. Повтор session.start, session.stop/session.cancel и resume не перезапускает подготовку, длительность или уже начатую остановку. Для остановки по внутреннему сроку или потере управления stopBeginMs/stopConfirmMs отсчитываются от решения перейти в stopping. Физический сигнал может начаться до подтверждения running; задержка подтверждения и прекращения сигнала входит в паспорт ОУ, durationMs не обещает точную акустическую длительность."
        },
        {
          "type": "p",
          "text": "Кроме обычного слота и слота session.stop/session.cancel существует один отдельный слот channel.ping. Каждый слот допускает не более одного запроса без ответа; повтор использует тот же id. При одновременной готовности порядок обработки: отмена, проверка связи, обычная работа. Проверка связи не обходит байты, уже поставленные в TCP; очередь отправки ограничена одним текущим кадром и по одному ожидающему кадру на слот. Запрос страницы и сервисная команда не могут бесконечно удерживать обычный слот. Байты файлов передаются по отдельному каналу bulk/1 и в управляющую очередь не помещаются."
        },
        {
          "type": "p",
          "text": "Полная задержка отмены считается как доставка полного кадра + cancelAdmissionMs + stopBeginMs/stopConfirmMs. TCP сохраняет порядок байтов, поэтому потерянные предыдущие байты задерживают session.stop. Паспорт развертывания ДОЛЖЕН задавать максимальные RTT, потери, время восстановления и допустимый сетевой перерыв, в которых проверяется предел. При полном отказе сети удаленная остановка не гарантируется: действует сохраненная stop/finish и maxDurationMs. Требование остановки независимо от сети обеспечивается местной защитой или независимым каналом с собственными полномочиями и подтверждением."
        },
        {
          "type": "heading",
          "id": "wire-size-contract",
          "text": "Кодирование в пределах размера кадра"
        },
        {
          "type": "p",
          "text": "maxXmlBytes=1024 ограничивает байты всего XML в UTF-8 после расшифрования, включая оболочку, имена, кавычки и XML-сущности; четырехбайтовый префикс длины в этот предел не входит. Лимиты 12 атрибутов, 32 элементов и глубины 4 сохраняются. Передатчик ДОЛЖЕН измерять фактическую сериализацию до отправки. Отступы в примерах не являются обязательными байтами. Не допускаются обрезание чисел, идентификаторов, текста или частичная передача команды ради размера."
        },
        {
          "type": "p",
          "text": "Если явно не задан более узкий тип, локальные идентификаторы resourceId, pointId, sourceId, conditionId, criterionId, testId, contentId, programId, коды и значения token содержат 1..32 символа [A-Za-z0-9_.:-]. Идентификаторы экземпляров, запросов, сеансов и снимков содержат ровно 32 строчных hex. cursor содержит 1..32 символа [A-Za-z0-9_.:-]; это непрозрачное значение, клиент его не вычисляет. Числа кодируются каноническими десятичными строками своего диапазона. Ограничение произвольного строкового значения 128 декодированных байт не разрешает идентификатор длиной 128 байт."
        },
        {
          "type": "p",
          "text": "Служебные идентификаторы не требуют XML-экранирования. Произвольный человекочитаемый текст остается UTF-8: при двойных кавычках передатчик заменяет только &, < и двойную кавычку стандартными сущностями; апостроф передается буквально, > ДОПУСКАЕТСЯ кодировать как &gt;. config.get передает параметр записями entry: id (token), facet=path|value, part=1..3, parts=1..3, type=bool|enum|int|counter|string, text до 64 декодированных байт UTF-8. Обе facet обязательны; каждая содержит все части 1..parts без повторов, type одинаков. Соединение text по part восстанавливает путь и значение до 128 байт каждое, без обрезания. Разделение проходит только между кодовыми точками UTF-8; короткая строка имеет одну часть, пустое значение — одну часть text=\"\"; путь не пустой. Уникальный id связывает параметр во всем снимке. Это формат частей значения, а не фрагментация XML. profiles.get parameter использует name/type/part/parts/value с тем же разбиением значения до 128 байт на части по 64 байта; остальные поля привязки профиля сохраняются. Сумма частей каждой facet или parameter не превышает 128 байт; третья часть нужна только при границе многобайтового символа. Размеры строк проверяются в байтах, а не в символах. device.describe использует version, manufacturer и softwareDeveloper как token до 32 ASCII-символов; последние два являются идентификаторами фактических изготовителей, их полные наименования приведены в паспорте. Обязательный элемент info protocolName=\"Открытый протокол систем оповещения\" обозначает только название протокола; обязательная маркировка компанией отсутствует."
        },
        {
          "type": "p",
          "text": "Команда с допустимыми максимальными значениями всех полей и четырьмя ресурсами ДОЛЖНА помещаться целиком. Ответы делятся только между целыми записями; сводка первой страницы, execution, completion, fault и timeline передаются отдельными страницами при необходимости. Запись с оболочкой и самым длинным cursor ДОЛЖНА помещаться в 1024 байта. Проверка максимальных размеров всех команд и примеров входит в комплект wire-budget текущей редакции. Она не заменяет проверку конкретного кодировщика изделия."
        },
        {
          "type": "p",
          "text": "После неудачного TCP/TLS-соединения или неуспешного resume клиент применяет отдельный для deviceId счетчик n, начиная с 0: перед следующей попыткой ожидает случайное равномерное время от B/2 до B включительно, B=min(30000, 500*2^min(n,6)) мс, затем увеличивает n. Одновременно ДОПУСКАЕТСЯ одна попытка управления этим УУ от экземпляра. Счетчик сбрасывается после 60000 мс устойчивого аутентифицированного обмена. Новая команда оператора и смена IP не сбрасывают задержку. Отказ forbidden прекращает автоматические попытки до исправления полномочий; несовместимый профиль требует изменения настройки. Уже прикрепленный канал продолжает heartbeat и приоритетный session.stop без этого ожидания. Эти интервалы учитываются в бюджете восстановления; они не являются гарантией доставки отмены при отказе сети."
        },
        {
          "type": "p",
          "text": "Предел команды 1024 байта — 1 КиБ, а не 1 МБ. Он относится только к UTF-8 XML. Четырехбайтовая рамка увеличивает прикладную передачу до 1028 байт. record_size_limit=512 включает внутренний тип TLS, поэтому одна запись без padding содержит не более 511 прикладных байт. Максимальный кадр требует как минимум трех записей, но разбиение может дать больше; приемник не предполагает совпадения кадра и TLS-записи. На каждую защищенную запись добавляются 5 байт TLS-заголовка, 1 байт внутреннего типа и 16 байт MGM-метки. При трех записях и отсутствии padding получается 1094 байта TCP payload; TCP/IP, ACK, повторы и Ethernet учитываются дополнительно. Шифротекст и открытый кадр не требуют одного общего буфера: разбор поточный, пиковая память подтверждается для конкретной реализации."
        },
        {
          "type": "p",
          "text": "В этой редакции сетевые адреса профиля — IPv4. Это явное ограничение совместимости, не свойство криптографии и не запрет разработать отдельный IPv6-профиль. Преобразование IPv6 в строку IPv4, усечение адреса и неявный выбор маршрута запрещены; неподдержанный адрес дает invalidArgument до воздействия. Расширение адресации требует схемы, области link-local, правил маршрута и проверки размеров, а не только разрешения двоеточия в поле."
        },
        {
          "type": "p",
          "text": "Структурная XML-схема wire-0.20.xsd, реестр wire-contract-0.20.json и проверка open_protocol_xml.py входят в нормативный комплект. XSD проверяет допустимые имена, лексические типы и словари; отдельная проверка применяет пределы UTF-8 в байтах, сочетания полей, формы каждой команды и правила контекста. Совместимость не устанавливается одной успешной проверкой XSD. Примеры имеют scenarioId и начальное состояние; одинаковые идентификаторы в разных самостоятельных сценариях не объединяются в одну историю. Сквозные трассы control-traces-0.20.json отдельно задают переходы, повтор и потерю ответа."
        },
        {
          "type": "heading",
          "id": "transport-slow-links",
          "text": "Бюджеты канала от 64 кбит/с"
        },
        {
          "type": "p",
          "text": "Расчетный нижний ориентир — доступные приложению 64 кбит/с суммарно по узкому участку в двух направлениях; для полнодуплексного канала дополнительно проверяется каждое направление. Паспорт фиксирует фактическую скорость, RTT, потери, туннели и длительности перерывов. 1024 байта XML занимают 128 мс при 64 кбит/с без обрамления, TCP/IP и защиты. При максимальном заполнении записей TLS и отсутствии padding кадр с 1024 байтами XML и четырехбайтовой длиной занимает три записи и 1094 байта полезной нагрузки TCP: 1028 байт кадра и 3*22 байта расходов TLS. Три записи — минимум для такого кадра; меньшие записи увеличивают их число и суммарные расходы. Например, разбиение прикладных байтов на 1+511+511+5 дает четыре записи и 1116 байт в TCP. record_size_limit ограничивает размер записи сверху, а не их количество. Суммарные расходы и повторы добавляются к времени доставки; 1024 не является размером пакета Ethernet."
        },
        {
          "type": "p",
          "text": "Базовые сетевые сроки: heartbeatIntervalMs=5000, controlLeaseMs=30000, frameAssemblyMs=10000, writeProgressMs=10000. ordinaryResponseMs=500 и cancelAdmissionMs=100 отсчитываются после получения полного допустимого кадра; физические stopBeginMs/stopConfirmMs не увеличиваются из-за медленного канала. Перед первым байтом session.stop могут находиться уже отправляемые данные TCP. Очередь управления ограничена прежними слотами, файловые и речевые потоки обслуживаются отдельно с приоритетом управления. Сквозной срок session.stop складывается из ожидания и передачи кадра, допуска, выдачи физической команды и подтверждения off."
        },
        {
          "type": "p",
          "text": "Отсоединение канала само по себе не заканчивает действующий lease. При восстановлении до его истечения продолжается прежнее конечное исполнение, если оно еще допустимо по всем остальным срокам. По истечении 30000 мс применяется сохраненная stop/finish; уже начатая остановка не отменяется возвращением сети. Локальная запись и программа с finish обеспечивают продолжение без канала; живая речь не получает обещания продолжения при исчезновении источника. Отзыв прав действует немедленно и не ожидает lease."
        },
        {
          "type": "p",
          "text": "deadlineUtcMs межузлового поручения ограничивает его доставку через АРМ и не обновляется посредниками. Конечный АРМ вычитает погрешность часов и бюджет последнего обмена, затем выбирает acceptBeforeMs в монотонной шкале УУ не позднее остатка срока и не далее 30000 мс от текущего uptimeMs. Если гарантированного остатка нет, session.start не отправляется. maxDurationMs начинается от принятия session.start и ограничивает подготовку и исполнение, а не срок маршрутизации. hopLimit ограничивает переходы независимо от этих трех бюджетов."
        }
      ]
    },
    {
      "id": "lifecycle",
      "title": "6. Сеанс, физический результат и потеря связи",
      "blocks": [
        {
          "type": "code",
          "label": "Предлагаемый автомат сеанса",
          "text": "starting -> running -> stopping -> finished\nstarting -----------> stopping -> finished\nstarting / running ------------> finished (только с установленным результатом)"
        },
        {
          "type": "table",
          "headers": [
            "Ответ или состояние сеанса",
            "Что подтверждает"
          ],
          "rows": [
            [
              "accepted",
              "Команда принята исполнителем. Усилитель еще может не быть включен."
            ],
            [
              "running",
              "Выполнен критерий начала из профиля ОУ. Критерий ДОЛЖЕН быть указан, а не выведен из TCP ACK."
            ],
            [
              "finished",
              "Логическое исполнение завершено с result и reason. Состояние выходов передается отдельно."
            ]
          ]
        },
        {
          "type": "table",
          "headers": [
            "Состояние ресурса",
            "Наблюдение"
          ],
          "rows": [
            [
              "off / commandedOff / onObserved / unknown / fault",
              "Отдельная ось состояния вместе с источником и уровнем доказательства. Отправленная команда выключения сама по себе не доказывает off."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "lifecycle-execution",
          "text": "Границы исполнения"
        },
        {
          "type": "p",
          "text": "На одном УУ в базовом профиле ДОПУСКАЕТСЯ один сеанс. Параметры запуска фиксируют ресурсы, версию контента и снимок конфигурации. maxDurationMs ограничивает все исполнение от принятия, включая подготовку и паузы. Истечение этого срока инициирует остановку, но не доказывает физического выключения неисправного ОУ. Срок приема session.start и таймаут подготовки задаются отдельно."
        },
        {
          "type": "p",
          "text": "Для начала и окончания передаются отдельные критерии и доказательства: принятие УУ, ответ ОУ, наблюдаемый режим, электрическое или акустическое наблюдение при наличии. Если профиль не дает обратной связи, выход остается commandedOff, а недоказанный физический итог — unknown. В ядре следующий запуск требует подтвержденного off; commandedOff и unknown сохраняют блокировку до восстановления по разделу 6. Успешный тест нельзя обосновать одним отправленным session.stop."
        },
        {
          "type": "p",
          "text": "Естественное окончание конечной программы отличается от исчерпания общего предела и от аварийного завершения. Причина maxDurationExceeded сама по себе не является успехом, даже если выход уже выключен. Перед дополнительным session.stop после автономного завершения проверяются контекст исполнения и правила драйвера: поздняя команда НЕ ДОЛЖНА прерывать другую работу ОУ."
        },
        {
          "type": "heading",
          "id": "lifecycle-failure",
          "text": "Ошибки и потеря связи"
        },
        {
          "type": "table",
          "headers": [
            "Сбой",
            "Поведение"
          ],
          "rows": [
            [
              "Потеря АРМ при сирене или встроенной записи",
              "Для purpose=real применяется сохраненная на конкретном УУ настройка onControlLoss: stop или finish; для exercise/technical применяется stop. Она фиксируется при запуске вместе с configRevision."
            ],
            [
              "stop",
              "Переход к остановке и проверке выходов. Возврат связи не запускает программу заново."
            ],
            [
              "finish",
              "Доигрывание конечной программы в принятом пределе. Принятая подготовка в starting продолжается в пределах prepareTimeoutMs и maxDurationMs."
            ],
            [
              "Наблюдатель остается на связи",
              "Его запросы не продлевают жизнь канала владельца."
            ],
            [
              "Потеря взаимодействия с ОУ при доступном УУ",
              "Отдельная авария источника ОУ. Политика реакции задается профилем; onControlLoss относится к АРМ."
            ],
            [
              "Перезагрузка УУ",
              "Новый bootId, старые команды не действуют. Автозапуск прошлого сеанса запрещен. ОУ могло остаться включенным: сначала проверка и согласование состояния."
            ]
          ]
        },
        {
          "type": "note",
          "text": "Для живой речи отдельно задаются потеря аудио, потеря управления, завершение потока, сброс буфера и возможная резервная запись. Значение stop/finish нельзя автоматически переносить на живую речь."
        },
        {
          "type": "heading",
          "id": "lifecycle-journal",
          "text": "Сохранение намерения"
        },
        {
          "type": "p",
          "text": "До передачи физического запуска УУ ДОЛЖЕН надежно записать намерение воздействия: версию формата, номер записи, deviceId, bootId, principalId, controlEpoch, cmdSeq, sessionId, нормализованное поручение, точные ресурсы с bindingGeneration, ревизии, onControlLoss и пределы исполнения. После завершения записи ее целостность проверяется чтением. Только затем разрешены accepted и передача задания ОУ. Отказ записи или проверки дает storageFault без воздействия."
        },
        {
          "type": "p",
          "text": "Постоянный журнал использует обнаружение оборванной записи и атомарный признак фиксации: контрольная сумма/длина/поколение, затем commit-маркер по гарантиям носителя. Единственная действующая запись не стирается до надежной фиксации замены. Незавершенная или поврежденная запись при загрузке означает unknown; ее нельзя интерпретировать как отсутствие запуска. Успешная фиксация намерения без физической передачи также не доказывает, что ОУ звучало. Новый bootId не позволяет повторить session.start автоматически."
        },
        {
          "type": "p",
          "text": "Закрытие записи воздействия ДОПУСКАЕТСЯ только после надежного сохранения результата и критерия off из профиля ОУ. Сбой Flash после воздействия сохраняет блокировку следующего session.start; адресная остановка текущего исполнения не ждет записи Flash и ДОПУСКАЕТСЯ по сохраненному в RAM контексту владельца. Если после reboot нельзя доказать происхождение воздействия или привязку ресурса, автоматическая остановка по поврежденному журналу запрещена; используется местная процедура восстановления. Паспорт ДОЛЖЕН задавать емкость, ресурс перезаписи, максимальное время фиксации и поведение при полном журнале."
        },
        {
          "type": "p",
          "text": "Приемочные отключения питания выполняются до и после записи намерения, commit-маркера, ответа accepted, передачи session.start ОУ, приема session.stop, подтверждения off, записи результата и стирания прежнего блока. Для каждой границы проверяются отсутствие повторного session.start, сохранение происхождения воздействия и достижимость предусмотренного восстановления."
        },
        {
          "type": "heading",
          "id": "lifecycle-recovery",
          "text": "Восстановление после питания"
        },
        {
          "type": "p",
          "text": "Восстановление имеет состояния reconciling, ready и localInterventionRequired, отдельные от сеанса. После reboot УУ меняет bootId, запрещает session.start и читает журнал незавершенного воздействия и наблюдения ОУ. Если сохраненная привязка совпадает и журнал указывает собственное незавершенное исполнение, заранее настроенная процедура recoveryStop может выдать ограниченную остановку его ресурсов. Эта процедура является восстановлением после сбоя; она не принимает произвольный сетевой session.stop без sessionId."
        },
        {
          "type": "table",
          "headers": [
            "Ситуация восстановления",
            "Правило"
          ],
          "rows": [
            [
              "Достоверный off",
              "Профиль задает источник, свежесть и необходимое число независимых наблюдений. После выполнения критерия УУ переходит в ready."
            ],
            [
              "Собственное незавершенное воздействие",
              "Применяется описанная в паспорте recoveryStop: точные ресурсы, допустимые команды, предел попыток и срок подтверждения. Отправленная команда дает commandedOff, а не off."
            ],
            [
              "Потеря взаимодействия с ОУ без перезагрузки",
              "Сохраняются владелец, сеанс и неизвестный результат. После восстановления взаимодействия с ОУ выполняется та же проверка выключения; новое включение само не возникает."
            ],
            [
              "Неизвестное происхождение включения",
              "Чужое или местное воздействие не присваивается старому сеансу. Автоматическая остановка без разрешенного правила профиля запрещена."
            ],
            [
              "ОУ без достаточной обратной связи",
              "Используется независимая обратная связь либо местное восстановление с подтвержденным физическим отключением. Нажатие сетевой кнопки квитирования или истечение таймера не снимает блокировку."
            ],
            [
              "Истек recoveryTimeoutMs",
              "localInterventionRequired с причиной, ресурсами и необходимым действием. УУ продолжает диагностику; новый достоверный критерий off позволяет завершить восстановление без нового включения."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Паспорт ОУ ДОЛЖЕН содержать recoveryTimeoutMs, recoveryStop либо запрет автоматического воздействия, критерий off, источник местного подтверждения и порядок снятия localInterventionRequired. Местное подтверждение привязано к текущим bootId и bindingGeneration и поступает через объявленный физический сервисный вход или доверенную сервисную процедуру; удаленный флаг оператора не заменяет это доказательство. Профиль без автоматической обратной связи обязан явно требовать местного вмешательства, а не обещать автоматическое восстановление."
        },
        {
          "type": "p",
          "text": "finish относится только к уже принятому конечному исполнению, включая его starting. Потеря управления не отменяет принятую подготовку, но не продлевает prepareTimeoutMs или maxDurationMs. Следующий самостоятельный сеанс, для которого session.start еще не принят, не исполняется. Все этапы уже принятого mode=program входят в единое поручение и продолжаются по правилам раздела 24. Перезапуск УУ всегда переводит ресурсы в восстановление, независимо от finish."
        },
        {
          "type": "heading",
          "id": "lifecycle-retention",
          "text": "Хранение результатов"
        },
        {
          "type": "table",
          "headers": [
            "Данные после завершения",
            "Гарантия хранения и чтения"
          ],
          "rows": [
            [
              "Результат через session.get",
              "Не менее resultRetentionMs от перехода в finished в пределах одной непрерывной загрузки УУ. Отключение АРМ, release и новая эпоха в той же загрузке не сокращают срок; права чтения проверяются отдельно."
            ],
            [
              "Намерение и итог в постоянном журнале",
              "Для восстановления сохраняются происхождение, ресурсы, решение о завершении и свидетельство критерия off. Незакрытое или сомнительное воздействие не удаляется по истечении resultRetentionMs. Полный набор страниц, checks и observations не обязан храниться в энергонезависимой памяти ядра."
            ],
            [
              "Перезагрузка УУ",
              "Старый bootId в session.get получает staleBoot. Запрос с новым bootId не является способом адресовать прошлую загрузку; неизвестный прежний sessionId дает notFound либо resultExpired по доступной истории. Ни один из этих отказов не доказывает отсутствия воздействия."
            ],
            [
              "Сведения восстановления",
              "Журнал прежней загрузки используется локальной процедурой recovery. Через device.state нового bootId доступны текущее recovery, состояние ресурсов и аварий. Это не выдача прежнего результата и не перенос его времени в новую шкалу uptimeMs."
            ],
            [
              "Архив между загрузками",
              "Чтение прежних результатов требует отдельного версионированного профиля постоянного журнала с адресацией исходного bootId, сроком хранения и правами. log.read профиля 0.20 адресует только журнал текущего bootId. АРМ сохраняет полученные результаты в своем журнале."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "lifecycle-normative",
          "text": "Временная шкала и допустимые результаты"
        },
        {
          "type": "p",
          "text": "Временная шкала исполнения является частью результата. Все отметки относятся к uptimeMs одного bootId, имеют тип uint64 в миллисекундах и не зависят от UTC. Отметка отсутствовавшего перехода опускается; число 0 является допустимым временем и не обозначает отсутствие перехода. Переподключение и повтор сообщения не изменяют уже зафиксированные отметки."
        },
        {
          "type": "table",
          "headers": [
            "Запись timeline в session.get",
            "Требование"
          ],
          "rows": [
            [
              "Кардинальность и страницы",
              "Ровно одна запись timeline во всем полном снимке результата. Она может находиться на любой странице; повтор или отсутствие записи нарушает полноту. sessionId ДОЛЖЕН совпадать с session.id первой страницы. Запись не включается в resourceCount/checkCount/observationCount/executionCount/faultCount. Все страницы result содержат одинаковый evaluatedAtMs - время создания снимка; времена наблюдений и уже состоявшихся переходов не превышают его."
            ],
            [
              "prepareDeadlineMs",
              "Обязательно. min(acceptedAtMs + prepareTimeoutMs, acceptedAtMs + maxDurationMs). Сложения выполняются без переполнения; непредставимый срок отклоняет session.start до воздействия."
            ],
            [
              "runningAtMs",
              "Присутствует тогда и только тогда, когда впервые достигнут критерий running. acceptedAtMs <= runningAtMs < prepareDeadlineMs. В program означает начало исполнения плана после общей подготовки; первая pause также начинает план, хотя выходы остаются off."
            ],
            [
              "runDeadlineMs",
              "Присутствует вместе с runningAtMs. Для сирены stopMode=duration min(runningAtMs + durationMs, acceptedAtMs + maxDurationMs); для stopMode=command acceptedAtMs + maxDurationMs, для записи/потока/теста/программы - acceptedAtMs + maxDurationMs. Естественный конец может наступить раньше."
            ],
            [
              "stopRequestedAtMs",
              "Время приема первой адресной отмены этого исполнения. Если отмены не было, атрибут отсутствует. Повторная отмена не сдвигает время."
            ],
            [
              "stoppingAtMs",
              "Время первой причины перехода к stopping: отмена, конец, предельный срок или отказ. Если первой причиной является отмена, равно stopRequestedAtMs; если истечение срока - этому сроку. Более поздняя отмена и повторная причина не сдвигают уже начатую остановку."
            ],
            [
              "stopIssuedAtMs",
              "Время фактической выдачи первой физической команды выключения после stoppingAtMs. Отсутствует, если команда не требовалась при уже достоверном off или не была выдана вследствие отказа. Не подменяется временем постановки в очередь."
            ],
            [
              "stopBeginDeadlineMs / stopConfirmDeadlineMs",
              "Оба обязательны при наличии stoppingAtMs. Равны stoppingAtMs + stopBeginMs и stoppingAtMs + stopConfirmMs; сроки фиксируются при переходе."
            ],
            [
              "finishedAtMs",
              "Обязательно только для state=finished. Не раньше acceptedAtMs и имеющихся runningAtMs/stoppingAtMs/stopIssuedAtMs. Итоговое доказательство off относится к промежутку от stoppingAtMs, либо acceptedAtMs при отсутствии физического запуска, до finishedAtMs включительно."
            ]
          ]
        },
        {
          "type": "p",
          "text": "При now >= любого действующего предельного срока сначала обрабатывается его истечение. При одновременном естественном конце и максимальном сроке выбирается maxDurationExceeded. Успех запрещен, если running достигнут после срока подготовки, физическая команда остановки выдана после stopBeginDeadlineMs либо off получен после stopConfirmDeadlineMs. Нарушенный бюджет отражается failed с причиной prepareTimeout, stopDispatchTimeout или stopConfirmTimeout; неизвестное выключение дает unknown/stopUnconfirmed. Позднее подтверждение выключения снимает блокировку ресурса через его текущее состояние, но не переписывает завершенный результат в успех."
        },
        {
          "type": "p",
          "text": "Наблюдение теста с validity=valid ДОЛЖНО принадлежать своей фазе: baseline - от acceptedAtMs до runningAtMs включительно; running - от runningAtMs включительно до stoppingAtMs исключительно; stopped - от stoppingAtMs до finishedAtMs включительно. Если верхняя граница фазы еще не наступила, используется время создания снимка. Наблюдение фазы, в которую исполнение не входило, передается с validity=unavailable, freshness=never, observedAtMs=0 и reason=phaseNotEntered; оно не удовлетворяет критерию успеха. Сохраненное старое показание не становится доказательством новой фазы."
        },
        {
          "type": "table",
          "headers": [
            "Терминальный result",
            "Допустимые причины и смысл"
          ],
          "rows": [
            [
              "succeeded",
              "completed при полном выполнении либо operatorStop для siren/recording/stream с confirmed всех ресурсов, без нарушения исполнения и с off в срок. Полнота передается отдельно в completion."
            ],
            [
              "cancelled",
              "operatorStop, когда условия succeeded/operatorStop не выполнены, либо controlLostStop: известное прекращение и off в срок. Недостаток обязательного доказательства дает unknown."
            ],
            [
              "failed",
              "maxDurationExceeded, prepareTimeout, stopDispatchTimeout, stopConfirmTimeout, sourceUnavailable, driverFault, storageFault, contentMismatch, mediaIncomplete, mediaClockFault, hardwareProtection или testFailed, equipmentFault: известное нарушение требования, выходы подтверждены off. Конкретная неисправность сохраняется в fault."
            ],
            [
              "unknown",
              "stopUnconfirmed, observationLost или recoveryUncertain: обязательное доказательство физического исхода отсутствует. Отправка команды и истечение таймера не разрешают другой итог."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Отсутствие наблюдений аварии требует freshness=never, validity=unavailable, state=unknown, observedAtMs=0 и changedAtMs=0. При наличии наблюдений changedAtMs <= observedAtMs; в снимке устройства observedAtMs <= uptimeMs снимка. Ответ channel.ping без controlEpoch ДОЛЖЕН содержать leaseRemainingMs=0, даже если на УУ существует другой владелец. Наблюдатель не продлевает его lease."
        },
        {
          "type": "heading",
          "id": "execution-confirmation",
          "text": "Принятие, подтверждение исполнения и завершение"
        },
        {
          "type": "p",
          "text": "Принятие команды (АП), подтверждение исполнения и завершение являются разными фактами. accepted подтверждает проверку и сохранение поручения, но не работу ОУ. Подтверждение исполнения означает, что выбранный ресурс фактически выполнил критерий начала заданного воздействия. Оно передается сразу после получения достаточного доказательства, без ожидания остановки. Обозначение «РП» не является термином или полем этого протокола; АРМ может использовать его для отображения подтверждения исполнения."
        },
        {
          "type": "p",
          "text": "Для каждого выбранного ресурса УУ ведет execution. В siren подтверждается начало заданного сигнала, в recording/stream — начало вывода отсчетов именно принятой записи/потока в исполнительный тракт. Прием файла, пакета RTP, включение усилителя без подачи принятого звука и TCP ACK недостаточны. Допустимые источники и граница физического наблюдения определены executionCriterion аппаратного профиля. reportedMode не объявляется акустическим измерением; отсутствие акустического датчика не запрещает явно объявленный цифровой или электрический критерий. УУ без достаточного доказательства сообщает unknown, а не confirmed."
        },
        {
          "type": "table",
          "headers": [
            "execution.status",
            "Значение и переход"
          ],
          "rows": [
            [
              "pending",
              "Критерий еще ожидается в пределах подготовки. Отправленная команда не меняет pending на confirmed. В finished этот статус запрещен."
            ],
            [
              "confirmed",
              "Критерий фактически выполнен; criterionId, sourceId, evidence и observedAtMs обязательны. Факт, время и доказательство сохраняются неизменными до конца хранения результата, в том числе после остановки или аварии."
            ],
            [
              "notExecuted",
              "Достоверно установлено, что воздействие данного прохода не начиналось. ДОПУСКАЕТСЯ при отмене до передачи физического запуска либо другом доказанном отсутствии воздействия; один таймаут или отсутствие ответа этого не доказывает."
            ],
            [
              "unknown",
              "Доказательства начала недостаточно. До завершения последующее достоверное наблюдение может уточнить статус; после finished исторический результат не переписывается."
            ]
          ]
        },
        {
          "type": "p",
          "text": "У сирены, записи, потока и теста имеется по одной execution на каждый ресурс. У program имеется запись для каждой пары ресурс/проход звучащего этапа; stageIndex и passIndex обязательны. Пауза execution не создает: запуск таймера паузы не подтверждает звучание. Для будущего прохода статус pending, после отмены не начатого прохода — notExecuted. session.started отражает переход running, а session.execution сообщает подтверждение отдельного ресурса. Эти события не взаимозаменяемы. Потеря события восстанавливается через session.get; снимок не вызывает повторного воздействия."
        },
        {
          "type": "table",
          "headers": [
            "Завершение",
            "Терминальный результат и сохраненные факты"
          ],
          "rows": [
            [
              "Нормальный конец",
              "succeeded/completed: полный план завершен, все необходимые execution подтверждены, обязательные проверки пройдены, все выходы off в срок."
            ],
            [
              "Оператор остановил сирену, запись или поток после подтвержденной работы",
              "succeeded/operatorStop при confirmed всех выбранных ресурсов, отсутствии нарушений исполнения и подтвержденном off в срок. Неполная запись отмечается completion=interrupted; это не ошибка начала воспроизведения."
            ],
            [
              "Оператор отменил до начала воздействия",
              "cancelled/operatorStop при notExecuted всех ресурсов и подтвержденном off. Положительное подтверждение исполнения отсутствует."
            ],
            [
              "Сработала лишь часть ресурсов либо остановлена неполная программа/проверка",
              "cancelled/operatorStop при известном исходе и off. Подтверждения сработавших ресурсов сохраняются, неполнота явно видна; общий успех всей программы/проверки не выдается. Неизвестное начало хотя бы одного ожидаемого воздействия дает unknown/observationLost."
            ],
            [
              "После подтвержденного начала возник отказ",
              "execution=confirmed сохраняется. Итог failed с причиной отказа при известном off; при неизвестном off — unknown/stopUnconfirmed. Отказ сохраняется в fault независимо от последующего session.stop и снятия аварии."
            ],
            [
              "Потеря управления с политикой stop",
              "cancelled/controlLostStop при известном исходе и off, либо unknown при недостатке доказательств. Это не штатная команда оператора. Политика finish продолжает только принятый конечный план."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Приоритет терминального итога: неизвестное выключение; установленный отказ исполнения; недостаток обязательного доказательства; разрешенная отмена; успех по установленным выше условиям. Нормальная остановка не заменяет уже установленный отказ. reason выбирается детерминированно: stopUnconfirmed при неизвестном off; иначе самая ранняя зафиксированная причина отказа, при равном времени — лексикографически по коду; иначе observationLost для недоказанного начала; иначе operatorStop/controlLostStop/completed по первой причине stopping. Все дополнительные отказы остаются в fault. Коды fault, совпадающие с причиной failed из словаря resultReason, сохраняют эту причину; остальные аппаратные коды с effect=failed дают equipmentFault. recoveryUncertain применяется при недоказанном происхождении в процедуре восстановления. Временные нарушения и maxDurationExceeded являются отказами, а не штатным концом."
        },
        {
          "type": "p",
          "text": "АРМ показывает раздельно принятие, подтверждение исполнения каждого ресурса, текущую аварию и завершение с причиной. Подтвержденный запуск не стирается поздней аварией, но текущая строка и общий итог не могут показывать безусловный успех при fault.effect=failed или unknown. Событие effect=none не ухудшает результат; degraded сохраняет подтверждение и видимый признак ухудшения. Конкретные названия колонок и цвета протокол не устанавливает."
        }
      ]
    },
    {
      "id": "delivery",
      "title": "7. Повторы, отмена и неизвестный исход",
      "blocks": [
        {
          "type": "p",
          "text": "В профиле обработка изменяющего запроса имеет две границы. Сначала проверяются целое обрамление, XML и его лимиты, общая оболочка, тип cmdSeq, полномочия, bootId, controlEpoch и канал. Отказ на этой границе НЕ ДОЛЖЕН менять highWater. После нее обычный новый cmdSeq выше highWater расходуется до проверки предметных аргументов, поддержки действия, срока и состояния ресурса. Любое решение второй границы, включая invalidArgument, unsupported, unsafeState, busy и startWindowExpired, ДОЛЖНО сохраняться в кеше. Неизвестный action с обычным номером и корректным контекстом изменяющего запроса также расходует номер и получает unsupported. Емкость кеша - не менее 8 обычных решений; решение зарезервированного session.stop/session.cancel сохраняется дополнительно до освобождения эпохи."
        },
        {
          "type": "p",
          "text": "Точный повтор в кеше возвращает прежнее решение. Другое нормализованное содержимое запроса при том же номере дает conflict. Старый номер без сохраненного решения и без адресной отмены получает resultExpired; highWater сохраняется. Для повтора сравнивается нормализованное содержимое полей, а не порядок XML-атрибутов. Транспортный id служит корреляции и не превращает тот же cmdSeq в новое действие. Ошибка парсинга до достоверного извлечения контекста не расходует номер."
        },
        {
          "type": "list",
          "items": [
            "Точный повтор сохраненной команды возвращает ее решение без нового действия. Совпадение номера при другом содержимом является конфликтом.",
            "Старый номер без решения и адресной отмены в кеше получает resultExpired. История не восстанавливается предположением по текущему idle.",
            "Прежний accepted из кеша не означает, что сеанс сейчас работает. Актуальное состояние читается отдельно.",
            "При reconnect сохраняются highWater, кеш и активный startSeq. После control.resume прежний канал теряет право изменяющих команд.",
            "Новый cmdSeq для повторной доставки session.start создает новое действие и поэтому не применяется как повтор.",
            "Гарантия отсутствия повторного исполнения ограничена контекстом bootId/controlEpoch. Exactly-once через отключение питания не заявляется."
          ]
        },
        {
          "type": "code",
          "label": "Отмена без полученного sessionId",
          "text": "session.start cmdSeq=1\nCANCEL cmdSeq=2, startSeq=1\n\nДоставка 1,2: остановить исполнение 1.\nДоставка 2,1: запретить поздний запуск 1.\nSTART cmdSeq=3, затем старый session.cancel target=1: сеанс 3 не затрагивается."
        },
        {
          "type": "heading",
          "id": "delivery-cancel",
          "text": "Отмена конкретного запуска"
        },
        {
          "type": "p",
          "text": "У cancel собственный новый номер; startSeq строго меньше cmdSeq. Если активного совпадения нет, ответ означает отсутствие активного исполнения с этой ссылкой и запрет позднего session.start. Это не утверждение, что звук никогда не включался. Один дополнительный слот разделяют session.stop и session.cancel; обычные изменения ждут разрешения этой гонки."
        },
        {
          "type": "p",
          "text": "Сохраненное решение исходного session.start имеет приоритет при его повторе. Если такого решения нет, но в ограниченном кеше есть подтвержденная отмена этого startSeq, запоздавший session.start получает startBlockedByCancel со ссылкой cancelSeq. Код означает запрет позднего исполнения и не доказывает историческое отсутствие запуска. После вытеснения этой записи возвращается resultExpired; highWater продолжает блокировать session.start. Для долговременного аудита АРМ сохраняет решения и происхождение отмены в своем журнале."
        },
        {
          "type": "heading",
          "id": "delivery-normalization",
          "text": "Сравнение повторных запросов"
        },
        {
          "type": "p",
          "text": "Нормализация повтора выполняется после проверки обрамления, безопасного XML и общей оболочки, до проверки предметного формата. Граница оболочки проверяет v/profile/id/action и типы bootId/controlEpoch/cmdSeq; изменяющий запрос требует полный контекст. Проверка прав, актуальности контекста и highWater выполняется УУ отдельно. Нормализация не выдает разрешения на исполнение. Неизвестный action с достоверным контекстом и неверные предметные поля сохраняются в представлении сравнения: unsupported и invalidArgument после этой границы расходуют номер по общему правилу."
        },
        {
          "type": "p",
          "text": "Из сравнения исключается только транспортный id. Сравниваются имена элементов, все остальные атрибуты и дочерние записи. Атрибуты упорядочиваются по имени; разрешенные XML-ссылки декодируются. Строки сравниваются по Unicode-кодовым точкам без изменения регистра, обрезки пробелов или Unicode-нормализации. Межэлементные пробелы не значимы. Значения по умолчанию не подставляются. Канонические числа и bool проверяются на предметной границе; ошибочные строки 01, -1 или banana до нее остаются различными строками и позволяют воспроизвести прежний отказ."
        },
        {
          "type": "p",
          "text": "resources сравнивается как множество только если содержит непустой набор resource с заданными уникальными id: записи сортируются по id. При дубликатах, отсутствии id или постороннем элементе сохраняется исходный порядок, без потери записей. Остальные последовательности сохраняют порядок. Неверное содержимое не становится допустимым от нормализации. Общая оболочка с неверным cmdSeq, поврежденный XML или превышенный лимит не дают представления принятого повтора и не расходуют номер."
        },
        {
          "type": "heading",
          "id": "rejection-contract",
          "text": "Права, занятость и структурированный отказ"
        },
        {
          "type": "p",
          "text": "TLS-аутентификация подтверждает principalId, но сама по себе не дает права управлять ОУ. Права задаются для действия, назначения real/exercise/technical/maintenance и области ресурсов. IP-адрес, знание sessionId или чужого controlEpoch не предоставляют полномочий. Непрошедший TLS участник не получает прикладной XML-ответ. Участник без требуемого права получает status=error и error.code=forbidden с обязательным detail до проверки занятости; чужие владелец, сеанс, ресурс и состояние в отказ не включаются. В явно выбранном plain применяется та же проверка локальных прав; логический principalId не является доказательством подлинности участника."
        },
        {
          "type": "table",
          "headers": [
            "error.code / detail",
            "Условие и последствие"
          ],
          "rows": [
            [
              "forbidden / roleDenied",
              "Нет права действия или назначения. Новая попытка требует изменения разрешений, а не ожидания освобождения."
            ],
            [
              "forbidden / scopeDenied",
              "Нет права на запрошенную область ресурсов. Внешний ответ не перечисляет существующие закрытые ресурсы."
            ],
            [
              "forbidden / ownerMismatch",
              "Попытка изменить, остановить или восстановить чужое исполнение. Знание его идентификатора не дает права; воздействие и таймеры неизменны."
            ],
            [
              "busy / ownershipHeld",
              "Разрешенный control.acquire конфликтует с действующим несовместимым владением. Сведения о другом участнике не раскрываются."
            ],
            [
              "busy / executionActive",
              "Разрешенный новый session.start конфликтует с действующим сеансом, в том числе собственным. Точный повтор исходного session.start возвращает сохраненное решение до этой проверки."
            ],
            [
              "busy / localControl",
              "Ресурс занят разрешенным местным управлением. Сетевой запуск не становится отложенным заданием."
            ],
            [
              "busy / serviceActive",
              "Несовместимая сервисная операция находится в неделимой стадии; действуют сроки и правила уступки обслуживанием."
            ],
            [
              "busy / capacityExhausted",
              "Исчерпана разрешенная квота снимков, опросов, медиаподготовок либо резерв результатов при превышении заявленной нагрузки."
            ],
            [
              "unsafeState",
              "Недоказанный off, восстановление, аппаратная блокировка или другое небезопасное состояние. Это не обычная занятость; ожидание busy не заменяет восстановление."
            ]
          ]
        },
        {
          "type": "p",
          "text": "error содержит code; detail обязателен только для forbidden, busy и conflict и берется из соответствующего закрытого словаря. Для startBlockedByCancel обязателен cancelSeq. Для остальных кодов detail отсутствует. Человекочитаемый текст не является машинным кодом. resource может указывать только доступный отправителю ресурс или класс session, ownership, snapshot, poll, media, service, history. При forbidden он отсутствует. Все общие отказы оболочки, прав и контекста применяются ко всем соответствующим командам, даже если карточка перечисляет только предметные ошибки."
        },
        {
          "type": "p",
          "text": "Порядок проверок: корректность обрамления/оболочки; права действия, назначения и области; bootId; принадлежность известной эпохи/исполнения участнику; актуальность эпохи и прикрепления; точный повтор; предметные ограничения; занятость/безопасность. Чужая известная эпоха дает forbidden/ownerMismatch, устаревшая собственная или неизвестная — staleEpoch; проверка прав всегда предшествует раскрытию контекста. При нескольких отказах одной группы: roleDenied, scopeDenied, ownerMismatch; для busy: ownershipHeld, localControl, executionActive, serviceActive, capacityExhausted. Обязательные сроки и сохраненная отмена проверяются согласно общему порядку session.start."
        },
        {
          "type": "p",
          "text": "forbidden и отказы контекста не расходуют acquireSeq/cmdSeq/highWater, не продлевают lease и не создают сеанс. Предметный busy обычной команды расходует ее номер и сохраняется как решение; повтор возвращает тот же отказ даже после освобождения. Новая попытка — новый номер и повторная проверка состояния. Исключения зарезервированных номеров session.stop/session.cancel/control.release сохраняются. busy никогда не ставит session.start в очередь. Очередь намерений real для acquire является только правилом арбитража: отдельные успешные acquire и session.start по-прежнему обязательны."
        },
        {
          "type": "p",
          "text": "Разрешенные session.stop/session.cancel своего исполнения не получают busy из-за того же исполнения, обычного запроса, заполненной очереди телеметрии или занятого сервиса. Приоритетный слот и резерв состояния отмены обеспечиваются до допуска session.start. Отказ остановки не подменяется положительным подтверждением выключения. Чужой session.stop не становится допустимым после таймаута или при purpose=real."
        },
        {
          "type": "table",
          "headers": [
            "Общий отказ",
            "Проверяемое условие",
            "Команды"
          ],
          "rows": [
            [
              "unsafeState",
              "Недоказанный off, блокировка восстановления или отсутствие обязательного независимого тракта до нового воздействия/обслуживания.",
              "control.acquire, control.release, session.start, audio.prepare, audio.group.prepare, audio.discard, content.begin, content.commit, config.stage, config.commit, network.stage, firmware.begin, firmware.verify, firmware.commit"
            ],
            [
              "notFound",
              "Адресованная существующая сущность не найдена после проверки прав; неизвестный session.cancel по startSeq создает запрет и этим правилом не заменяется.",
              "session.start, session.stop, audio.prepare, audio.group.prepare, audio.state, audio.finish, audio.discard, media.hello, transfer.hello, firmware.verify, firmware.commit, firmware.cancel, firmware.status, content.commit, content.cancel, content.status, config.commit, config.cancel, network.confirm, network.cancel"
            ],
            [
              "storageFault",
              "Не удалось прочитать или атомарно сохранить необходимое постоянное состояние, журнал намерений, пакет либо образ. Отказ не является успешным исполнением и не подтверждает off.",
              "control.acquire, control.resume, control.release, session.start, session.stop, session.cancel, config.stage, config.commit, config.cancel, network.stage, network.confirm, network.cancel, firmware.begin, firmware.verify, firmware.commit, firmware.cancel, firmware.status, content.begin, content.commit, content.cancel, content.status, config.get, content.describe"
            ],
            [
              "staleEpoch",
              "Указанная эпоха не является допустимым контекстом команды; освобожденная эпоха проверяется по отдельному правилу повтора control.release.",
              "channel.ping, media.hello, transfer.hello"
            ],
            [
              "staleAttachment",
              "Действующее управляющее соединение не совпадает с прикрепленным каналом либо привязкой операции.",
              "channel.ping, media.hello, transfer.hello"
            ]
          ]
        },
        {
          "type": "p",
          "text": "Одинаковые полномочия не требуют одинакового набора предметных отказов: чтение, подготовка и фиксация проверяют разные состояния. Таблица общих отказов и предметная карточка действуют совместно; итоговый список каждой команды и машинный wire-contract формируются из одних записей. unsafeState не обозначает обычную занятость, notFound не раскрывает чужую сущность, storageFault не означает noSpace. Сбой сохранения при остановке не отменяет обязательную попытку физического session.stop и не создает доказательства его результата."
        },
        {
          "type": "heading",
          "id": "delivery-admission-order",
          "text": "Номера, слоты и точка принятия решения"
        },
        {
          "type": "p",
          "text": "cmdSeq оболочки имеется только у изменяющих предметных команд. protocol.hello, control.acquire и control.resume используют собственные правила; channel.ping, чтения, media.hello и transfer.hello не расходуют cmdSeq и не вытесняют решения из его кеша. cmdSeq внутри selection команды command.get является адресом прежнего решения. Наличие controlEpoch в чтении не превращает чтение в изменение."
        },
        {
          "type": "p",
          "text": "На управляющем соединении одновременно допускаются один обычный запрос, один запрос адресной session.stop/session.cancel и один heartbeat. Чтение занимает обычный слот, но не кеш решений. Новый запрос в занятом слоте получает busy/detail=capacityExhausted; точный повтор текущего id присоединяется к ожидающему решению. При одновременной готовности полных кадров порядок обработки: session.stop/session.cancel, heartbeat, обычный запрос. Это порядок допуска, а не право отменить уже состоявшийся физический результат."
        },
        {
          "type": "p",
          "text": "Решение по изменяющему запросу линеаризуется при атомарной фиксации результата допуска вместе с highWater и записью повтора; session.start фиксирует также намерение до воздействия. Проверки прав, контекста, окна и наличия слота предшествуют этой точке. Для отмены одновременно сохраняется адрес startSeq и запрет позднего запуска. Непосредственно перед физическим session.start исполнитель повторно проверяет сохраненную отмену, актуальность поколения прикрепления и срок подготовки. Если session.cancel принят первым, воздействия нет; если session.start принят первым, отмена останавливает именно его, а успешное принятие session.start не становится доказательством звучания."
        },
        {
          "type": "p",
          "text": "Резервные номера session.stop/session.cancel и control.release не проходят окно cmdSeqWindow, не меняют обычный highWater и не занимают его восемь записей. Отказ вне окна, forbidden и busy из-за занятого слота не расходуют номер, не освежают lease и не вытесняют решения. Отказ по предметным условиям после допуска сохраняется по общему правилу изменяющих команд. Повтор с измененным содержимым получает conflict, без нового воздействия."
        },
        {
          "type": "p",
          "text": "После потери ответа control.release клиент устанавливает новое соединение того же режима, выполняет protocol.hello и повторяет исходный запрос с прежними id, bootId, controlEpoch и cmdSeq. До проверки требования действующего прикрепления УУ проверяет прежнюю идентичность участника и кеш control.release завершенной эпохи. Точный повтор возвращает исходный released; измененный запрос — conflict. Известная завершенная эпоха без подробного решения дает resultExpired, неизвестная или чужая эпоха — staleEpoch либо forbidden с соблюдением неразглашения. Смена загрузки дает staleBoot. Восстановление результата не прикрепляет канал, не выдает владение и не продлевает таймеры. Оно не исполняет новую команду control.release: если исходное освобождение не было принято, ТРЕБУЕТСЯ обычное восстановление своей еще существующей эпохи через resume."
        },
        {
          "type": "p",
          "text": "При tls прежняя идентичность доказывается действующим ключом того же principalId; отозванный участник получает forbidden. При plain сопоставляются только прежнее открытое пространство clientId и местные права: криптографического доказательства личности нет. Открытый повтор никогда не получает результат защищенной эпохи. При утрате кеша отсутствие подробности не считается доказательством неисполнения; перед новым acquire читается актуальное состояние."
        }
      ]
    },
    {
      "id": "ou-profiles",
      "title": "8. Профили ОУ: ВАУ и Сирена",
      "blocks": [
        {
          "type": "p",
          "text": "Профиль перечисляет реальный состав оборудования, допустимые режимы, ресурсы, интерфейс к ОУ, критерии начала и выключения, таймауты и источники наблюдений. ВАУ обозначает оборудование для подачи сигналов и речевых сообщений; Сирена - оборудование для подачи звукового сигнала. Наличие каждой функции и точки диагностики объявляется отдельно и подтверждается испытаниями реализации."
        },
        {
          "type": "table",
          "headers": [
            "Свойство",
            "ВАУ",
            "Сирена"
          ],
          "rows": [
            [
              "Основное питание",
              "Наличие питания и доступные измерения; напряжение и число фаз задаются профилем",
              "Наличие питания и доступные измерения; напряжение и число фаз задаются профилем"
            ],
            [
              "Резервное питание",
              "Наличие источника, состояние и доступные измерения объявляются отдельно",
              "Наличие источника, состояние и доступные измерения объявляются отдельно"
            ],
            [
              "Общие отказы",
              "Неисправность ОУ и усилителя при наличии раздельных источников",
              "Неисправность ОУ и исполнительного механизма при наличии раздельных источников"
            ],
            [
              "Специфические наблюдения",
              "Состояния установленных выходных линий и признаки работы усилителя",
              "Признаки работы сирены, состояния фаз и токов при наличии соответствующих датчиков"
            ],
            [
              "Аудио",
              "Поддержанные источники речи, записи, состояние тракта и буфера",
              "Записи и внешние аудиовходы являются отдельными возможностями; тип Сирена сам по себе их не обещает"
            ]
          ]
        },
        {
          "type": "p",
          "text": "Признак отсутствия питания - дискретная авария, а не численное измерение напряжения. Низкое напряжение АКБ не дает процента заряда. Число позиций в ответе фидеров не равно числу установленных линий: состав берется из проверенной конфигурации. Состояния off/open/overload/on не дают отдельного надежного energized при неисправности."
        },
        {
          "type": "heading",
          "id": "ou-profiles-driver",
          "text": "Драйвер и нижний интерфейс"
        },
        {
          "type": "p",
          "text": "Контракт драйвера определяется функционально: описание возможностей, привязка ресурсов, подготовка, запуск, остановка, получение наблюдений, соблюдение сроков и выдача результата. Наличие выделенного программного драйвера и обработчика входящих байтов НЕ ТРЕБУЕТСЯ; имена функций и структуру программного обеспечения выбирает изготовитель. При обмене с ОУ без идентификаторов корреляции операции сериализуются. Позднее подтверждение после таймаута не подтверждает новую команду. Смена bindingGeneration исключает применение ответов и наблюдений прежней привязки к новому действию."
        },
        {
          "type": "p",
          "text": "Дедупликация сетевых команд сама по себе не дает гарантии однократного физического действия. Если управляющее воздействие запуска передано исполнительному тракту, а результат неизвестен, УУ не повторяет воздействие автоматически: сначала выполняются доступная проверка состояния и оценка достаточности доказательства. Общий режим \"запись\" не подтверждает конкретное содержание. После перезапуска или неоднозначного подтверждения исход остается unknown. Профиль задает правила повторов и проверки выключения; для интерфейса с обменом сообщениями также задаются правила удаления устаревших данных и выдержки перед следующим обменом."
        },
        {
          "type": "p",
          "text": "При наличии двух путей к одному ОУ, например Ethernet и радиоканала через внешний шлюз, АРМ хранит одну логическую цель и связь маршрутов с общим физическим ресурсом. Таймаут первого пути не доказывает отсутствие запуска и не разрешает повторный session.start по резерву. Профиль резервирования ДОЛЖЕН обеспечить исключение двух исполнителей, сохранение происхождения и последующее согласование. Радиоканал не объявляется встроенной возможностью УУ."
        },
        {
          "type": "note",
          "text": "Профиль оборудования определяет состав ОУ, способ управления, источники подтверждения, границу наблюдения и пределы реакции. Параметры линии, адресация и формат кадров задаются только для использующего их интерфейса. Подтверждение по внутреннему интерфейсу само по себе не является доказательством слышимости оповещения населением."
        },
        {
          "type": "heading",
          "id": "ou-profiles-passport",
          "text": "Машинный паспорт физического исполнения"
        },
        {
          "type": "p",
          "text": "profiles.get обязательно для каждого объявленного ресурса. Запрос selection resourceId либо resourceId/snapshotId/cursor. Ответ profiles: snapshotId, resourceId, bindingGeneration, capabilityRevision, more/cursor. Первая запись profile содержит id/revision/kind=vau|siren; остальные записи имеют форму parameter name/type/part/parts/value, type=bool|enum|int|counter|string, name до 32 ASCII-символов. Полное значение до 128 байт передается частями по 64 байта по разделу 5; все части каждого имени обязательны. Записи executionCriterion задают подтверждение начала по разделу 17. Пара id/revision однозначно обозначает полный набор параметров. АРМ сохраняет его до session.start; смена любого параметра меняет ревизию и bindingGeneration."
        },
        {
          "type": "table",
          "headers": [
            "Параметры паспорта",
            "Обязательные значения"
          ],
          "rows": [
            [
              "prepareTimeoutMs / stopBeginMs / stopConfirmMs",
              "Положительные целые миллисекунды; stopBeginMs <= 1000, stopBeginMs <= stopConfirmMs <= 10000. Для технического теста пределы не выше 100 и 3000 мс."
            ],
            [
              "runningEvidence / offEvidence",
              "reportedMode|electrical|acoustic|localConfirmation и соответствующие runningPointId/offPointId из каталога; observedAtMs и качество обязательны. Команда, отправленная без наблюдения, не является ни одним из этих доказательств."
            ],
            [
              "evidenceMaxAgeMs / stableSamples",
              "1..1000 мс и 1..8 последовательных независимых наблюдений. Повтор чтения одного sampleSeq не увеличивает stableSamples. Для теста независимые off/control-каналы задаются independentStop=true и independentOffPointId."
            ],
            [
              "continuousSupported / intermittentSupported",
              "bool. При continuous сигнал не имеет программных пауз. Для intermittent обязательны onMs/offMs:100..60000, firstPhase=on, finalPartial=truncate, toleranceMs=0..100. Частотное/акустическое описание включается параметром signalSpecId и его signalSpecRevision; оно является частью неизменного паспорта, а не выводится из имени pattern."
            ],
            [
              "durationMinMs / durationMaxMs / durationStepMs",
              "100..3600000, min<=max, step>=1. Запрошенная длительность попадает в диапазон и кратна step от min; иначе unsupported, без округления."
            ],
            [
              "recoveryStopAttempts / recoveryTimeoutMs",
              "0..3 попыток и 1..10000 мс. При ненулевом числе попыток профиль задает точные собственные ресурсы и независимый критерий off. Нельзя останавливать чужой обнаруженный запуск по одной догадке о происхождении."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Соответствие сетевому протоколу АРМ–УУ не требует определенного внутреннего интерфейса УУ–ОУ и не означает совместимость с произвольным усилителем или сиреной. Паспорт изделия ДОЛЖЕН описывать состав исполнительного тракта, способ взаимодействия, границу наблюдения, критерии подтверждения начала и состояния off, обработку отказов и конечные сроки. Для встроенного исполнительного тракта профиль указывает integrated и границу наблюдения. Публикация внутреннего программного интерфейса, электрической схемы или собственного протокола изготовителя условием соответствия сетевому протоколу не является."
        },
        {
          "type": "p",
          "text": "Если изготовитель дополнительно объявляет совместимость сменных УУ и ОУ разных производителей по определенному интерфейсу, он ДОЛЖЕН указать открытый версионированный профиль этого интерфейса. Профиль определяет физическое взаимодействие, управляющие воздействия, наблюдения, ошибки, повторы, отмену, критерии начала и выключения и конечные сроки. Параметры линии, адресация, границы и контроль целостности кадров задаются при использовании обмена сообщениями; для дискретных сигналов задаются уровни, назначение и временные соотношения. Эти требования относятся к дополнительно заявленной совместимости интерфейса УУ–ОУ."
        },
        {
          "type": "p",
          "text": "При объявлении такой совместимости profiles.get передает lowerProtocolId/lowerProtocolRevision и lowerSpecUri/lowerSpecHash обычными parameter. lowerSpecHash содержит хеш Стрибог-256 от точной последовательности байтов описания: 256 бит, 32 байта, 64 строчных hex. Идентификатор и ревизия имеют тип string/counter, URI и hash — string, предел значения 128 байт. URI имеет схему https; описание доступно независимо от изготовителя АРМ. При отсутствии отдельной декларации совместимости интерфейса УУ–ОУ передавать эти параметры НЕ ТРЕБУЕТСЯ; это не ограничивает соответствие сетевому протоколу АРМ–УУ."
        },
        {
          "type": "heading",
          "id": "signal-spec-registry",
          "text": "Реестр описаний сигналов"
        },
        {
          "type": "p",
          "text": "signalProfileId/signalProfileRevision этапа программы ссылаются на id/revision записи profiles того же пакета конфигурации, kind=siren. Это существующий аппаратный профиль, а не отдельный реестр. Его targetIds ДОЛЖНЫ покрывать все цели данного этапа. Параметры signalSpecId/signalSpecRevision этого профиля ссылаются на id/revision записи signalSpecs пакета open-alert-config/5. Отсутствие ссылки, другая ревизия или несогласованность параметров отклоняют весь пакет с invalidArgument."
        },
        {
          "type": "table",
          "headers": [
            "Поле signalSpec",
            "Контракт"
          ],
          "rows": [
            [
              "id / revision",
              "id: token; revision: положительный uint64. Пара обозначает неизменное описание. Коллекция signalSpecs сортируется по id и не содержит повторов; пустая допустима, если нет профилей сирены."
            ],
            [
              "kind / waveform",
              "kind=tone|linearSweep|mechanical; waveform=sine|square|mechanical. mechanical требует waveform=mechanical, синтезируемые сигналы требуют sine либо square. Изделие объявляет только реализованные сочетания."
            ],
            [
              "minHz / maxHz / toleranceHz",
              "Канонические целые: 1 <= minHz <= maxHz <= 20000; 0 <= toleranceHz < minHz. Для tone minHz=maxHz. Для mechanical границы обозначают допустимую основную частоту в установившемся режиме; форма и гармоники не нормируются этим видом."
            ],
            [
              "periodMs",
              "Для linearSweep: 100..60000 мс, minHz < maxHz. Первая половина периода линейно повышает частоту от minHz до maxHz, вторая понижает обратно; первая фаза начинается с minHz. Для tone/mechanical равно 0."
            ]
          ]
        },
        {
          "type": "p",
          "text": "profiles.get передает полное описание выбранного сигнала обычными parameter: signalSpecId, signalSpecRevision, signalKind, signalWaveform, signalMinHz, signalMaxHz, signalToleranceHz, signalPeriodMs. Первые четыре имеют тип string, counter, enum, enum соответственно; остальные counter. Значения совпадают с записью signalSpecs. Для intermittent onMs/offMs задают внешнюю огибающую; каждый on начинает сигнал с первой фазы. Эти поля описывают задание ОУ, но не заявляют измерение звукового давления. Критерии физического подтверждения остаются в executionCriterion. Иная форма сигнала требует согласованного расширения; имя изготовителя или pattern не подменяет описание."
        }
      ]
    },
    {
      "id": "sensors",
      "title": "9. Датчики и каталог ресурсов",
      "blocks": [
        {
          "type": "p",
          "text": "Каталог имеет уровни controller, ethernet, ouInterface и ou/{ouId}, а также линии, входы, выходы и точки наблюдения. Возможности контроллера и подключенного ОУ разделены. Новый производитель добавляется драйвером и профилем без переопределения смысла базовых команд."
        },
        {
          "type": "p",
          "text": "Таблица ниже описывает логическую модель, а не один XML-элемент. inventory.get передает постоянное описание точки, sensors.read - изменяемое показание. Связь: pointId, capabilityRevision и bindingGeneration в контексте снимка. Источник sourceKind отвечает на вопрос, откуда получено значение; evidence описывает уровень доказательства физического результата. Эти поля не взаимозаменяемы."
        },
        {
          "type": "table",
          "headers": [
            "Группа полей точки",
            "Содержание"
          ],
          "rows": [
            [
              "Идентичность",
              "resourceId, parentId, pointId, sourceId; capabilityRevision и bindingGeneration"
            ],
            [
              "Смысл",
              "kind, тип bool/enum/int/counter, единица и масштаб; список enum; пороги и применимость"
            ],
            [
              "Источник",
              "sourceKind: localInput, ouReply, controllerMetric или derived"
            ],
            [
              "Наличие",
              "presence: present, absent, expectedMissing или unknown; поддержка отделена от физического наличия"
            ],
            [
              "Способ чтения",
              "readMethod: cached, refreshable или checkOnly; допустимый интервал и время устаревания"
            ],
            [
              "Значение",
              "value плюс validity: valid/invalid/unavailable; freshness: fresh/stale/never и причина"
            ],
            [
              "Время и последовательность",
              "sampleSeq, observedAtMs, changedAtMs, evaluatedAtMs, maxAgeMs и bootId; качество абсолютных часов отдельно"
            ]
          ]
        },
        {
          "type": "heading",
          "id": "sensors-catalogue",
          "text": "Каталог и достоверность показаний"
        },
        {
          "type": "p",
          "text": "В каталоге описание точки разделяется на самостоятельные записи point с id и facet=identity|meaning|source|acquisition. identity содержит resourceId/parentId/sourceId; meaning - kind, тип, единицу и масштаб; source - sourceKind/presence; acquisition - readMethod/maxAgeMs. Дополнительные enum и threshold передаются отдельными записями с pointId в том же снимке; точные поля и проверка полного каталога заданы в разделе 17. Каждая запись ДОЛЖНА укладываться в 12 атрибутов, а целая страница с оболочкой - в 1024 байта. Продолжение переносит целые записи, а не обрывки XML; конец каталога устанавливается только последней страницей снимка. Значения не дублируют весь каталог."
        },
        {
          "type": "p",
          "text": "Чтение кеша не обновляет observedAtMs. Если ОУ не передает время измерения, observedAtMs обозначает время получения ответа УУ. После потери источника наблюдений ОУ старое аварийное значение сохраняется с признаком stale/sourceLost; отсутствие ответа не очищает аварию. Собственные метрики контроллера при этом могут оставаться свежими."
        },
        {
          "type": "p",
          "text": "Температура, ток, напряжение, сопротивление линии, акустический контроль и внешние пожарные входы могут описываться общей моделью. Они объявляются в capabilities только при подтвержденном аппаратном источнике. Каждая объявленная точка ДОЛЖНА иметь определенный источник и правила качества данных."
        },
        {
          "type": "p",
          "text": "Линия, фаза питания и вход ЧС имеют разные kind и собственные pointId. Несколько входов не сворачиваются в один bool. Профиль сохраняет исходный код и версию декодирования наряду с нормализованным значением. Обрыв, перегрузка/КЗ, выключение, неизвестность и отсутствие точки различаются; общий бит перегрузки/КЗ не позволяет выдумать отдельный диагноз. Зарезервированные биты не создают установленные линии. Варианты ОУ с одним общим ответом и с детализацией по фазам имеют разные каталоги."
        }
      ]
    },
    {
      "id": "polling",
      "title": "10. Опросы, проверки и аварии",
      "blocks": [
        {
          "type": "p",
          "text": "Предусмотрены два вида опроса: без запуска ОУ и с запуском ОУ. Опрос с запуском - самостоятельный сценарий проверки работоспособности под воздействием, с включением оборудования и сбором результатов в рабочем состоянии. Чтение кеша датчиков не заменяет ни один из этих сценариев."
        },
        {
          "type": "table",
          "headers": [
            "Операция",
            "Смысл и ограничения"
          ],
          "rows": [
            [
              "sensors.read",
              "Чтение доступного снимка без аппаратного воздействия. Значение, возраст и качество передаются вместе."
            ],
            [
              "Опрос без запуска: poll.start / get / cancel",
              "Свежий опрос источников без включения ОУ. operationId связывает результаты; состав ожидаемых точек фиксируется на старте. Отмена относится только к своему заданию опроса."
            ],
            [
              "diagnostics.read",
              "Инженерные показатели программного и аппаратного состояния. Не выполняет скрытую перезагрузку или тест выходов."
            ],
            [
              "Опрос с запуском: session.start mode=test",
              "Профиль testId с kind=activationPoll. Запускает ОУ, собирает предусмотренные наблюдения, останавливает и проверяет выключение. Использует общий исполнитель сеансов, права владельца, sessionId и startSeq."
            ]
          ]
        },
        {
          "type": "table",
          "headers": [
            "Этап опроса с запуском",
            "Что фиксируется"
          ],
          "rows": [
            [
              "Подготовка / starting",
              "Точные ОУ и выходы, testId/testRevision, допустимые эффекты, ожидаемые точки, исходные показания и состояние ресурсов. Проверяются права и отсутствие конфликта."
            ],
            [
              "Запуск / starting",
              "Команда включения ОУ и подтверждение по профилю. accepted означает прием задания, а не состоявшееся включение."
            ],
            [
              "Наблюдение / running",
              "Показания при запущенном оборудовании и результаты проверок: например, доступные признаки работы сирены, усилителя, линий или питания. Только реально поддержанные источники."
            ],
            [
              "Остановка / stopping",
              "Штатное завершение конечной программы либо отмена/таймаут. Выполняется остановка именно ресурсов этого сеанса."
            ],
            [
              "Проверка выключения / stopping -> finished",
              "Отдельный результат возврата в допустимое состояние. Неизвестное выключение не считается успехом и сохраняет блокировку ресурса."
            ]
          ]
        },
        {
          "type": "p",
          "text": "tests.list задает неизменный каталог планов тестов с типизированными критериями и наблюдениями фаз. Поддержка mode=test требует выполнения формата каталога настоящего раздела и фиксации testId/testRevision при session.start."
        },
        {
          "type": "heading",
          "id": "polling-execution",
          "text": "Запуск и оценка проверки"
        },
        {
          "type": "p",
          "text": "Для запуска нужны controlEpoch, cmdSeq и права управления выбранными ресурсами. Повтор session.start не включает ОУ повторно. Если sessionId известен, отмена выполняется session.stop; если ответ запуска потерян - session.cancel по startSeq. Команда poll.cancel относится только к опросу без запуска и не является неявной остановкой тестового сеанса."
        },
        {
          "type": "p",
          "text": "Результат опроса с запуском читается через session.get и связан с session.finished: исходные и рабочие наблюдения, checks[] по каждому ресурсу с passed/failed/inconclusive/skipped, полнота набора и отдельное подтверждение выключения. У каждого наблюдения сохраняются фаза, источник, время и качество. Полный сбор данных может выявить неисправность; успешная остановка не превращает проваленную проверку в passed."
        },
        {
          "type": "p",
          "text": "Функциональная часть получает passed только при выполнении всех обязательных проверок; отсутствие нужной обратной связи дает inconclusive. Общий успех требует также подтвержденного завершения воздействия. Истечение таймера в АРМ не доказывает прекращения теста: УУ ограничивает сеанс своим maxDurationMs и отдельно сообщает физический результат остановки."
        },
        {
          "type": "p",
          "text": "Опрос с запуском не перехватывает действующее оповещение. При занятости возвращается busy; отложенный неожиданный запуск после освобождения не создается. При purpose=technical потеря управления всегда применяет stop. Для программы purpose=real сохраняются настроенная stop/finish и конечный предел; exercise всегда применяет stop и предел 1080000 мс с условиями допуска учений. Восстановление сети или взаимодействия с ОУ не запускает проверку заново. Другие воздействия требуют явной политики профиля."
        },
        {
          "type": "p",
          "text": "Состояние опроса queued/running/finished/cancelled отделено от complete - покрытия ожидаемых точек. Каждая точка использует validity/freshness/reason каталога; недоступная точка не исчезает из результата. При смене каталога результаты не смешиваются с новым набором точек."
        },
        {
          "type": "p",
          "text": "Описание теста содержит эффекты none/readBus/switchOutput/audible/pauseAudio/resetInterface/writeStorage. Слово \"диагностика\" не делает звучащую проверку безопасным чтением. Фоновый опрос без запуска не превращается автоматически в опрос с запуском. Плановый опрос с запуском задается отдельно: полномочный инициатор, профиль, ресурсы, разрешенное окно и срок приема. Пропущенное окно не создает позднего автоматического включения. Периоды опроса ограничиваются возможностями взаимодействия с ОУ и бюджетом нагрузки; session.stop имеет приоритет перед новой сервисной работой."
        },
        {
          "type": "heading",
          "id": "polling-alarms",
          "text": "Аварии и переход к оповещению"
        },
        {
          "type": "p",
          "text": "conditionId обозначает условие аварии, alarmId - конкретный эпизод. Подтверждение оператором в АРМ не устраняет неисправность и не сбрасывает аппаратную защелку. Ее сброс, если доступен, требует отдельного разрешенного действия и подтвержденного результата."
        },
        {
          "type": "p",
          "text": "Паспорт развертывания ДОЛЖЕН задавать alarmTransitionMaxMs: максимальное время от возникновения санкционированной реальной тревоги на АРМ до принятия УУ нового session.start после технической проверки. Это предел принятия команды, а не начала звучания. В него входят оставшееся исполнение теста, выключение, сохранение результата, освобождение эпохи, ожидание повторной попытки acquire, доставка и обработка acquire и session.start. Учитываются также незавершенный запрос acquire и получение его busy, если они задерживают следующую попытку. Допуск проверки требует соблюдения этого бюджета и при предусмотренной потере ее АРМ. При отсутствии исправного независимого пути выключения и наблюдения off техническая проверка запрещена. Адресная отмена допускается только уполномоченным владельцем; самостоятельная тревога не получает права прерывать чужое исполнение."
        },
        {
          "type": "p",
          "text": "Переход от технической проверки к реальной тревоге выполняет текущий владелец: адресный session.stop/session.cancel проверки, подтвержденный off и автоматическое освобождение технической эпохи, затем новый acquire purpose=real и session.start с purpose=real. Каждый шаг имеет самостоятельный результат; при unknown второй session.start запрещен. Другой АРМ получает busy и обращается к текущему исполняющему узлу по разрешенной ветви, а не повышает приоритет в обход владения. Назначение real само по себе не дает прав прерывания."
        },
        {
          "type": "p",
          "text": "Для mode=test назначение ДОЛЖНО быть technical, maxDurationMs ДОЛЖНО быть не более 30000. Паспорт теста задает также stopBeginMs/stopConfirmMs. Планировщик допускает проверку только в разрешенное окно и при допустимом бюджете перехода к тревоге. После off и сохранения результата УУ автоматически освобождает техническую эпоху. Продолжение любого плана требует нового acquire; heartbeat не сохраняет прежнее владение."
        },
        {
          "type": "heading",
          "id": "polling-normative",
          "text": "Доступность тревоги после технического теста"
        },
        {
          "type": "p",
          "text": "Технический сеанс выполняется с onControlLoss=stop независимо от настройки обычного оповещения. Его maxDurationMs не превышает 30000 мс, stopBeginMs не превышает 100 мс, stopConfirmMs не превышает 3000 мс. После его завершения, подтверждения off и сохранения результата УУ автоматически освобождает эпоху; heartbeat не удерживает завершенный технический сеанс. Допуск теста требует исправного независимого пути подтверждения/обеспечения выключения и доступного места постоянного журнала."
        },
        {
          "type": "p",
          "text": "Для каждого ресурса, допускающего звучащую техническую проверку, аппаратный профиль ДОЛЖЕН задавать независимый от тестируемого канала способ выключения и наблюдения off: управляемое отключение выхода с обратной связью либо отдельный канал управления и подтверждения. Потеря подтверждения по основному интерфейсу ОУ НЕ ДОЛЖНА оставлять исправный резервный путь неиспользованным. Процедура восстановления ограничена stopConfirmMs. При неисправности независимого пути звучащая проверка запрещена, а диагностика без воздействия остается доступна."
        },
        {
          "type": "p",
          "text": "При принятом тесте и отсутствии более раннего действующего или ожидающего real-запуска alarmTransitionMaxMs первого живого участника очереди не превышает 35000 мс. Сумма включает до 30000 мс оставшегося исполнения, до 3000 мс остановки и до 2000 мс последующего перехода: сохранение результата, освобождение эпохи, ожидание следующей попытки acquire, обмен acquire и принятие session.start. На сохранение результата и освобождение отводится не более 500 мс; оставшиеся 1500 мс являются общим бюджетом ожидания, сети и обработки обеих команд, а не только сетевой доставки. Если в момент освобождения еще выполняется прежний acquire, в этот бюджет входит получение busy и интервал до повторной попытки 500..2000 мс. АРМ тревоги ДОЛЖЕН заранее установить управляющий канал выбранного режима tls либо plain. Выбранные пределы всех составляющих ДОЛЖНЫ укладываться в alarmTransitionMaxMs; разрешенный интервал повтора 2000 мс сам по себе не дает гарантии перехода за 35000 мс при максимальной длительности теста. Если сумма превышает предел, maxDurationMs теста уменьшается до допуска либо тест не выполняется. Потеря владельца применяет stop по истечении lease и не отменяет общего предела. Установление нового соединения после возникновения тревоги в эту гарантию не входит."
        },
        {
          "type": "p",
          "text": "Отказ хранилища после воздействия не превращает неизвестный результат в свободный ресурс. Для системы с требованием оповещения при единичном отказе проект развертывания ДОЛЖЕН предусматривать независимый резервный исполнитель и тракт доставки сообщения без общего отказавшего хранилища, контроллера и ключа активации. Переключение маршрута относится к тому же runId и не дает права прерывать чужое исполнение. Требуемая отказоустойчивость задается составом независимых трактов и предельным временем перехода; таймаут ожидания не считается восстановлением."
        },
        {
          "type": "heading",
          "id": "polling-formats",
          "text": "Каталог тестов и задания опроса"
        },
        {
          "type": "p",
          "text": "tests.list обязательно при mode=test. Запрос содержит selection с необязательным testId либо snapshotId/cursor. Ответ tests содержит snapshotId, capabilityRevision, more, cursor при more=true. Запись test: id, revision, resourceId, kind=activationPoll, maxDurationMs, stopBeginMs, stopConfirmMs, effects — непустой список имен через запятую, в лексикографическом порядке, без пробелов и повторов, из testEffect. Отдельные записи check: testId, id, resourceId, phase=baseline|running|stopped, pointId, operator=eq|ne|lt|le|gt|ge, expected (значение типа точки). Запись observation: testId, id, resourceId, pointId, phase, type, sourceId и sourceKind. Все ID локального типа имеют 1..32 ASCII-символа; revision и сроки - uint64. Для каждого ресурса обязательны наблюдения трех фаз и минимум один критерий. none ДОПУСКАЕТСЯ только единственным эффектом; audible требует switchOutput, resetInterface требует readBus. Указываются все эффекты теста. pauseAudio/resetInterface/writeStorage требуют maintenance и не допускаются в session.start mode=test; для них нужен отдельный явно объявленный сервисный профиль. Полный каталог фиксируется до session.start; смена ревизии дает conflict до воздействия."
        },
        {
          "type": "p",
          "text": "Проверка comparison выполняется над типизированным значением: eq/ne для bool/enum/int/counter, остальные операторы только для int/counter; единица и scale берутся из каталога точки. Наблюдение удовлетворяет критерию только при validity=valid, freshness=fresh и принадлежности нужной фазе по timeline. Отсутствие фазы или показания дает skipped с причиной; ложное сравнение - failed/testFailed. Совокупный succeeded требует passed всех обязательных критериев и off всех ресурсов."
        },
        {
          "type": "p",
          "text": "poll.start не включает выходы и не использует владение оповещением. Запрос poll содержит clientOperationId (32 hex), deadlineMs (uptime УУ, не дальше 30000 мс), refresh=cache|readBus и 1..8 point id. clientOperationId дедуплицируется отдельно в principalId/bootId до deadlineMs+resultRetentionMs; другой набор полей дает conflict. Ответ accepted с operation id/clientOperationId/state=queued. ДОПУСКАЕТСЯ одно задание на участника и не более двух на УУ; превышение дает busy. Получение наблюдений от ОУ выполняется с более низким приоритетом, чем управление, и не продлевает deadlineMs. Неизвестная точка дает invalidArgument до создания задания."
        },
        {
          "type": "p",
          "text": "poll.get: selection operationId либо operationId/snapshotId/cursor. Ответ poll с snapshotId, operationId, state=queued|running|finished|cancelled, expectedCount, complete, evaluatedAtMs, more/cursor; записи point используют формат sensors.read. При finished или cancelled полный снимок содержит каждую ожидаемую точку; неполученная точка имеет validity=unavailable, freshness=never, observedAtMs=0 и reason=deadlineExceeded|cancelled|sourceUnavailable. complete=true означает покрытие выбранного множества, а не исправность. poll.cancel: poll operationId/clientOperationId, аутентифицированный создатель задания; ответ cancelled operationId. Отмена идемпотентна, не отменяет session и не удаляет уже полученные наблюдения. Запросы опроса не продлевают lease."
        }
      ]
    },
    {
      "id": "diagnostics",
      "title": "11. Диагностика, события и снимки",
      "blocks": [
        {
          "type": "p",
          "text": "Базовая доставка событий в проектном профиле - pull: АРМ вызывает events.read с bootId и afterSeq последнего обработанного события. Ответ содержит ограниченную страницу записей event и nextSeq; more=true означает, что на момент снимка страницы оставались записи. more=false завершает текущую выборку, но не подписывает клиента на будущие события. Самопроизвольных сообщений event в этом профиле нет; запрос занимает обычный слот, session.stop/session.cancel - отдельный."
        },
        {
          "type": "p",
          "text": "УУ хранит ограниченный кольцевой журнал, общий для читателей, и не накапливает отдельную неограниченную очередь для медленного АРМ. Если afterSeq предшествует сохраненной истории, ответ сообщает gap=true и oldestSeq. АРМ ДОЛЖЕН получить новый согласованный снимок с границей eventSeq, затем читать журнал после этой границы. Повторный gap обрабатывается по ограниченному алгоритму восстановления ниже. Курсор АРМ сдвигается только после обработки страницы; повторная доставка не создает повторное событие. Переполнение транспортной очереди обрабатывается по разделу 5."
        },
        {
          "type": "table",
          "headers": [
            "Слой",
            "Минимальное содержание"
          ],
          "rows": [
            [
              "Контроллер",
              "bootId, причина запуска, uptime, длительность цикла, запас стека, таймеры, Flash, очереди и потери журнала. Причина сбоя неизвестна, если нет достоверного сохраненного следа."
            ],
            [
              "Ethernet",
              "Link, восстановления, назначение и состояние каждого сокета, таймауты. Закрытый резервный сокет не является неисправностью."
            ],
            [
              "Управление",
              "Владелец, состояние канала, возраст последнего подтвержденного обмена, highWater, сеанс и причины завершения."
            ],
            [
              "Взаимодействие с ОУ",
              "Профиль и поколение, отказы, сроки и возраст последних наблюдений; при обмене сообщениями также ошибки кадров и поздние ответы."
            ],
            [
              "Медиа / выходы",
              "Поток, буфер, потери, underrun, фактические наблюдения ресурсов и неизвестные исходы."
            ],
            [
              "Счетчики",
              "Область boot/stream/session/persistent, поколение, правила насыщения и сброса. Значения разных поколений не вычитаются."
            ]
          ]
        },
        {
          "type": "p",
          "text": "События session.started/finished/execution/fault, sensor.qualityChanged, resource.changed, inventory.changed и alarm.raised/cleared/qualityChanged получают eventSeq и stateRevision. poll.finished относится к отдельному расширению. В примерах атрибут seq записи event кодирует eventSeq. Потеря части кольцевого журнала явно обозначается gap. Клиент получает актуальный снимок и продолжает с новым курсором. Снимок восстанавливает текущее состояние, но не утраченную историю."
        },
        {
          "type": "heading",
          "id": "diagnostics-snapshots",
          "text": "Полнота снимков"
        },
        {
          "type": "p",
          "text": "Многостраничное чтение снимков использует snapshotId, bootId, ревизии соответствующего контейнера и ограниченный cursor. Истечение снимка возвращает snapshotExpired; клиент начинает заново. Страницы разных снимков не объединяются. events.read читает изменяющийся журнал через afterSeq/nextSeq без snapshotId/cursor и без snapshotExpired; повтор может вернуть другой доступный диапазон или gap. В базовом варианте поток событий не фильтруется, чтобы не скрывать разрывы последовательности."
        },
        {
          "type": "p",
          "text": "Набор ожидаемых точек фиксируется ревизией каталога и областью запроса. Каждая страница связана с запросом и снимком, имеет явное продолжение или конец. Повторы не увеличивают полноту, перестановка не меняет нумерацию точек, конец по таймауту не заменяет отсутствующие страницы. Снимок согласован относительно модели, но observedAtMs отдельных измерений могут различаться."
        },
        {
          "type": "p",
          "text": "При подключении АРМ получает снимок с границей eventSeq, затем запрашивает события после этой границы через events.read. Изменения сохраняются журналом в пределах его емкости; утрата обнаруживается через gap. Уже активные аварии входят в снимок независимо от появления нового фронта. Для состояния учитываются bootId и ревизия: запоздавшее событие не перезаписывает более новое значение. Восстановление истории и начальный снимок не являются новым внешним запуском."
        },
        {
          "type": "p",
          "text": "Для каждого полного каталога и снимка паспорт задает максимальное число страниц P и условия канала. ДОЛЖНО выполняться P * (RTTmax + responseMaxMs + transferMaxMs) + retryBudgetMs <= snapshotReadMaxMs <= 27000 мс. Слагаемые относятся к одному последовательному обмену; retryBudgetMs включает ограниченные повторы для заявленных потерь, transferMaxMs - сериализацию обоих направлений. Оставшиеся 3000 мс являются запасом до истечения снимка. При P=100 и RTT=500 мс одни сетевые обмены занимают около 50000 мс: такой каталог на таком канале не соответствует ядру. Изготовитель обязан ограничить объявляемый объем или предложить другой профиль; автоматическое перечитывание не устраняет невозможность уложиться в срок."
        },
        {
          "type": "p",
          "text": "Поддержка многотысячного парка подтверждается паспортом системы: число УУ и наблюдателей, частота events.read, число удерживаемых владений, пиковые события, общий перерыв сети и скорость восстановления. При N владельческих соединений ping с периодом heartbeatIntervalMs=5000 дает N/5 запросов в секунду в среднем, а опрос событий раз в T секунд - N/T; ответы и прочие чтения учитываются отдельно. АРМ ДОЛЖЕН ограничивать пулы соединений, применять квоты и разброс reconnect. Приемка ДОЛЖНА включать одновременное восстановление при session.start/session.stop на заявленной нагрузке."
        },
        {
          "type": "p",
          "text": "Базовое право events.read дает доступ ко всему журналу одного УУ; частичное право чтения датчиков не дает этого права. Таким образом базовый курсор не скрывает запрещенные события под видом gap. Изменение прав прерывает чтение и требует новой проверки доступа. Фильтрованный журнал является отдельным профилем с собственным пространством курсоров."
        },
        {
          "type": "p",
          "text": "Паспорт телеметрии содержит eventCapacity, eventRateMax, eventBurstMax, snapshotReadMaxMs и eventDrainMinPerSec. Для заявленной нагрузки ДОЛЖНО выполняться eventCapacity > eventBurstMax + eventRateMax * snapshotReadMaxMs / 1000 и eventDrainMinPerSec > eventRateMax, с запасом на плановые перерывы чтения. Частота учитывается после разрешенной обработки дребезга; число наблюдателей входит в расчет общей пропускной способности. Пределы являются условиями испытаний, а не следствием размера счетчика."
        },
        {
          "type": "p",
          "text": "После трех последовательных gap или snapshotExpired при восстановлении АРМ прекращает автоматический полный перечитывающий цикл, помечает историю incomplete и переходит к периодическому device.state с сохранением управления и текущих аварий. Повтор восстановления выполняется с ограниченной задержкой 5, 10, 20, затем 60 секунд, со случайным разбросом до 20 процентов; переход обратно в синхронное состояние требует успешного снимка и чтения его продолжения без gap. Потерянные исторические переходы остаются потерянными, даже если текущий снимок получен."
        },
        {
          "type": "p",
          "text": "Дребезг подавляется у физического источника по явно настроенному правилу до создания событий. Утвержденные переходы alarm.raised/cleared, изменение качества и результаты сеансов НЕ ДОЛЖНЫ объединяться с потерей перехода. Частые численные измерения могут агрегироваться только в объявленный интервал с min/max/last и счетчиком; переполнение журнала всегда видно как gap. nextSeq равен последнему переданному seq, а для пустого ответа без gap - исходному afterSeq."
        },
        {
          "type": "heading",
          "id": "diagnostics-formats",
          "text": "Форматы диагностики и журнала"
        },
        {
          "type": "p",
          "text": "diagnostics.read: selection scope=controller|ethernet|control|ouInterface|media либо snapshotId/cursor. Ответ diagnostics с snapshotId, uptimeMs, more/cursor; записи metric name/type/value/unit, где type=bool|int|counter|enum, name и unit до 32 ASCII-символов. Обязательны uptimeMs, bootReason, activeConnections, commandQueueDepth, rejectedFrames, lostEvents и значения используемых очередей; счетчик сбрасывается только при новом bootId. Чтение не сбрасывает состояния и счетчики. log.read: selection afterSeq/limit, limit=1..8; ответ log oldestSeq/nextSeq/gap/more и записи entry seq/atMs/level/code/sessionId (последний необязателен). Правила непрерывности и gap совпадают с events.read; level=info|warning|error. Длинный свободный текст, ключи и пользовательское аудио в журнал не включаются."
        },
        {
          "type": "p",
          "text": "command.get: selection controlEpoch/cmdSeq с bootId в оболочке; только участник исходной команды. Ответ command cmdSeq/state=accepted|rejected|expired; action обязательно при сохраненном решении и отсутствует при expired; при доступном решении дочернее тело исходного ответа (session, cancellation, released, prepared, upload, transaction, network, clock, operation либо error) совпадает с сохраненным первоначальным решением. Транспортные to/id исходного ответа не переносятся. При вытеснении возвращается expired, которое не означает отсутствия воздействия и не разрешает повторный session.start новым номером. Максимальный сохраненный ответ ДОЛЖЕН помещаться в общий XML-лимит вместе с оболочкой command."
        },
        {
          "type": "heading",
          "id": "fault-impact",
          "text": "Ошибка исполнения и диагностическое событие"
        },
        {
          "type": "p",
          "text": "Ошибки до accepted передаются только отказом команды: они не создают положительного подтверждения исполнения или фиктивного session.finished. После accepted неисправность передается как состояние/событие оборудования и, при влиянии на сеанс, как fault в его результате. Обычная остановка оператором, снятие аварии и штатный переход off не являются неисправностями."
        },
        {
          "type": "table",
          "headers": [
            "Код условия оборудования",
            "Значение"
          ],
          "rows": [
            [
              "powerLost / phaseLost / batteryLow",
              "Потеря питания / фазы / низкий резерв питания. Влияние определяется реально поддержанным режимом и резервированием."
            ],
            [
              "lineOpen / lineShort / amplifierFault",
              "Обрыв линии / короткое замыкание / отказ усилителя. Указывается точный resourceId или pointId линии."
            ],
            [
              "ouLinkLost / outputMismatch / sourceLost",
              "Потеря интерфейса ОУ / несоответствие наблюдаемого выхода заданию / потеря источника звука."
            ],
            [
              "overTemperature / hardwareProtection",
              "Перегрев / срабатывание защиты. Сам факт защиты не подтверждает off без установленного критерия."
            ],
            [
              "storageFault / mediaIncomplete / mediaClockFault / driverFault",
              "Отказ хранения / нарушение доставки звука / частоты воспроизведения / исполнительного драйвера."
            ],
            [
              "vendorFault",
              "Условие изготовителя; conditionId указывает опубликованное описание. Новые произвольные значения закрытого code запрещены."
            ]
          ]
        },
        {
          "type": "p",
          "text": "inventory.get передает condition id/pointId/resourceId/mode/code/severity/effect для каждого объявленного аварийного условия; mode — конкретный режим либо all. conditionId эпизода alarm ссылается на эту запись. severity=info|warning|critical задает важность, effect=none|degraded|failed|unknown — влияние на выполнение выбранного режима. Это разные оси: авария незадействованной линии не превращается автоматически в отказ другого ресурса. Аппаратный профиль определяет обязательные условия, источник, правила возникновения/снятия и зависимости ресурсов; состав и смысл меняют capabilityRevision. Неизвестное качество условия не означает его снятия."
        },
        {
          "type": "p",
          "text": "Состояния active/cleared/unknown, alarmId, времена и правила восстановления эпизода сохраняются. Новый fault фиксируется при возникновении влияющего условия, а также для уже действующего условия при accepted. fault.effect=failed инициирует остановку; unknown означает потерю обязательного доказательства и также инициирует остановку. degraded допускает продолжение только по явно объявленному профилю. fault.alarmId связывает нарушение с точным эпизодом оборудования. Снятие обновляет clearedAtMs, но не удаляет исторический fault и не превращает failed в succeeded. Переход из degraded в failed сохраняется новой записью. Поздний session.stop не очищает отказ."
        },
        {
          "type": "p",
          "text": "session.fault передает один fault с sessionId; alarm.raised/cleared/qualityChanged по-прежнему передают ровно один alarm. События исполнения и fault публикуются атомарно с stateRevision соответствующего изменения. Переполнение кольца дает gap; текущие аварии восстанавливаются через device.state, исторические нарушения принятого сеанса — через session.get. УУ резервирует емкость обязательного результата до accepted; потеря обязательной истории дает storageFault, а не пустой успешный результат."
        },
        {
          "type": "p",
          "text": "Паспорт развертывания использует зарегистрированный параметр alarmTransitionMaxMs: целое 100..35000 мс для допуска технической проверки, не меньше суммы выбранного maxDurationMs теста, его stopConfirmMs и бюджета закрытия/нового управления. Это параметр развертывания, не обязательный limit device.capabilities. При невозможности соблюсти его тест запрещен. Для обычных исполнений 1 <= stopBeginMs <= 1000 и stopBeginMs <= stopConfirmMs <= 10000; для technical прежние более строгие пределы 100/3000 мс сохраняются. Верхние границы ограничивают соответствующий профиль, а не объявляют неисправное оборудование физически выключенным: пропуск срока остается отказом либо unknown по правилам результата."
        },
        {
          "type": "p",
          "text": "Нижняя допустимая граница объявленного таймера — 1 мс. Объявленный срок включает дискретность таймера, худшее планирование, обмен с ОУ, фильтрацию обратной связи и погрешность измерения. Изделие ДОЛЖНО подтвердить верхнюю границу испытанием при максимальной совместной нагрузке; округление вниз и использование средней задержки вместо гарантированного предела запрещены. Если фактический предел превышает максимум профиля, соответствующая функция не объявляется."
        }
      ]
    },
    {
      "id": "security",
      "title": "12. Защищенный обмен и ключи",
      "blocks": [
        {
          "type": "p",
          "text": "Защищенный TCP-обмен использует TLS 1.3 с российским криптонабором Кузнечик-MGM и Стрибог-256 по Р 1323565.1.030-2020 и RFC 9367. Требование российских алгоритмов распространяется на шифрование, имитозащиту, хеширование, выработку ключей, аутентификацию и защиту хранилищ ключевой информации. В режиме tls участники ДОЛЖНЫ завершить взаимную аутентификацию до прикладных действий. Связь principalId с проверенным ключом устанавливает конфигурация доверия, а не поле из XML."
        },
        {
          "type": "p",
          "text": "Защита ДОЛЖНА связывать сообщение с устройством, участником, направлением, подключением и поколением ключей. Аутентификация и целостность проверяются до исполнения. Прикладные полномочия и защита от повторного session.start сохраняются поверх криптографического канала. Открытое управление ДОПУСКАЕТСЯ только явно выбранным plain при securityPolicy=optional; автоматическое отключение защиты после ошибки запрещено."
        },
        {
          "type": "table",
          "headers": [
            "Элемент профиля защиты",
            "Нормативное правило"
          ],
          "rows": [
            [
              "Аутентификация и шифрование",
              "Общие идентификаторы алгоритмов и версии профиля, согласование канала, целостность и аутентификация сообщений, тестовые векторы независимых реализаций."
            ],
            [
              "TLS и прикладное обрамление",
              "TLS обеспечивает защиту записей; внутри его потока находятся uint32 BE длины и XML. Лимит XML не является размером TLS record, сертификата или сообщения handshake."
            ],
            [
              "Ключи и параметры TLS",
              "Защищенный режим использует только tls13-gost-kuz-mgm/1, параметры которого зафиксированы ниже. Поколения ключей, доверие и роли задает конфигурация; nonce, ключи записей и record sequence обслуживает российский TLS-профиль."
            ],
            [
              "Полные идентификаторы участников",
              "ID аутентифицируются либо однозначно связываются при согласовании канала. Молчаливое усечение запрещено."
            ],
            [
              "Модель доставки",
              "Транспорт, порядок, потери, повторы и восстановление проверяются для каждого объявленного профиля; свойства одного транспорта не переносятся на другой автоматически."
            ]
          ]
        },
        {
          "type": "p",
          "text": "В режиме tls ядро использует индивидуальные PSK и временный обмен на группе GC256A. Сборка прикладного сообщения ведется по длине в расшифрованном потоке; границы TLS records и TCP-сегментов с границами XML не отождествляются. Криптографические счетчики независимы от cmdSeq: повтор прикладного поручения сохраняет cmdSeq, но отправляется через TLS как новые прикладные данные с корректным состоянием защиты. Повтор TCP-сегмента обслуживается транспортом и не запускает шифрование повторно."
        },
        {
          "type": "p",
          "text": "Требование российских алгоритмов сохраняется для всех криптографических механизмов профиля. При управлении tls аудио и bulk также используют TLS; при явно выбранном plain ДОПУСКАЕТСЯ открытый TCP без проверки ключей. Защищенный session.start сам по себе не удостоверяет отдельное открытое аудио, поэтому смешение защищенного управления с открытым медиаканалом запрещено. UDP и multicast в rtp-alert/4 отсутствуют. Ссылка на TLS, DTLS или SRTP не разрешает иной криптонабор."
        },
        {
          "type": "heading",
          "id": "security-provisioning",
          "text": "Внесение доверия"
        },
        {
          "type": "p",
          "text": "Производственный профиль ДОЛЖЕН определить индивидуальное внесение ключей, роли АРМ и конфигуратора, отзыв и замену, физическое восстановление доступа и аудит без секретов. Защита сетевого канала АРМ–УУ не распространяется автоматически на внутренний интерфейс УУ–ОУ; его границы доверия и меры защиты определяются исполнением изделия. Поставка применяет криптографические средства с требуемым подтверждением соответствия требованиям ФСБ России для конкретного исполнения и условий использования; перечень алгоритмов в спецификации не заменяет такого подтверждения."
        },
        {
          "type": "p",
          "text": "Первичный индивидуальный сервисный ключ устанавливается доверенной производственной процедурой отдельно от общего образа прошивки. Общие заводские пароли, ключи в открытом XML, журналах или обычном экспорте конфигурации запрещены. Рабочие ключи вводит участник с подтвержденными сервисными полномочиями. Ключи в АРМ, конфигураторе, резервных копиях и переносимых контейнерах защищаются средствами на российских алгоритмах с требуемым для поставки подтверждением соответствия требованиям ФСБ России. Формат контейнера определяется применяемым средством защиты; собственный парольный контейнер протокол не вводит."
        },
        {
          "type": "heading",
          "id": "security-hash",
          "text": "Хеширование содержимого"
        },
        {
          "type": "p",
          "text": "Образы прошивок, содержимое записей и защищаемые манифесты используют hashAlgorithm=streebog256. Хеш вычисляется по точной последовательности байтов объекта от смещения 0 до объявленного размера, без дополнения, нормализации текста и преобразования кодировки. XML hash содержит 64 строчных hex-символа: по два на каждый байт результата в порядке выдачи хеш-функции, без разворота массива. Для составного манифеста хешируются байты его зафиксированного формата, а не произвольная сериализация XML."
        },
        {
          "type": "p",
          "text": "Контроль порядка байтов: для трех байт 61 62 63 (ASCII abc, без перевода строки) hash равен 4e2919cf137ed41ec4fb6270c61826cc4fffb660341e0af3688cd0626d23b481. Скачиваемые digest-vectors содержат также пустое сообщение и пример RFC 6986, переведенный из записи битового вектора в порядок байтов файла. Эти векторы определяют представление hash на проводе независимо от API криптобиблиотеки."
        },
        {
          "type": "p",
          "text": "Совпадение хеша подтверждает целостность относительно доверенного значения, но не право издателя на выпуск образа. Проверка подлинности обновления отделена от проверки хеша и от защиты транспорта. Если профиль обновления использует подпись, он применяет ГОСТ Р 34.10-2012 со Стрибогом и указывает формат подписываемых данных и доверие к издателю; загрузка по защищенному каналу не объявляется автоматически secure boot."
        },
        {
          "type": "heading",
          "id": "security-evidence",
          "text": "Подтверждение совместимости"
        },
        {
          "type": "p",
          "text": "Паспорт совместимости реализации ДОЛЖЕН назвать TLS-библиотеку и версию, настройки сборки, криптографический backend, источник энтропии, пиковые память/стек и время полного handshake на целевой аппаратуре. Проверяются независимая сторона, неверные Finished/метки, открытые точки, счетчики/TLSTREE/KeyUpdate, отзыв, отказ энтропии и массовое восстановление соединений при одновременном управлении ОУ и работе локальных таймеров. RFC 9367 имеет статус Informational; ссылка на него не означает сертификацию изделия или одобрение криптонаборов IETF. Реализация ДОЛЖНА соблюдать численные бюджеты выбранного профиля при указанной совместной нагрузке."
        },
        {
          "type": "heading",
          "id": "security-tls",
          "text": "Параметры защищенного канала"
        },
        {
          "type": "p",
          "text": "При выборе режима tls обязательный профиль защиты управляющего ядра - tls13-gost-kuz-mgm/1: TLS 1.3 с российскими криптографическими алгоритмами по Р 1323565.1.030-2020 и RFC 9367. Нормативные параметры приведены ниже и в паспорте security-0.20.json. Обе стороны допускают только этот криптонабор, режим PSK с ECDHE и группу GC256A; отсутствие совпадения завершает handshake до передачи XML. Подмена алгоритма или автоматическое отключение защиты запрещены."
        },
        {
          "type": "table",
          "headers": [
            "Параметр защиты",
            "Обязательное значение"
          ],
          "rows": [
            [
              "Идентификатор профиля",
              "tls13-gost-kuz-mgm/1"
            ],
            [
              "TLS cipher suite",
              "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L, 0xc103"
            ],
            [
              "Шифрование и имитозащита",
              "Кузнечик-MGM, ключ 256 бит, полная метка 16 байт"
            ],
            [
              "Хеширование",
              "Стрибог-256, ГОСТ 34.11-2018 / ГОСТ Р 34.11-2012; hashAlgorithm=streebog256"
            ],
            [
              "Аутентификация",
              "Взаимная, индивидуальный external PSK 256 бит; psk_dhe_ke"
            ],
            [
              "Обмен ключами",
              "GC256A, 0x0022; id-tc26-gost-3410-2012-256-paramSetA; кодирование точек по RFC 9367"
            ],
            [
              "Выработка ключей",
              "TLS key schedule с HMAC/HKDF на Стрибог-256; ключи записей через TLSTREE и KDF_GOSTR3411_2012_256 по RFC 9367/7836"
            ],
            [
              "Размер защищенной записи",
              "record_size_limit=512, включая внутренний тип и padding; XML до 1024 байт передается потоком"
            ]
          ]
        },
        {
          "type": "p",
          "text": "PSK длиной 256 бит выводится по open-alert-trust/2 из случайного индивидуального секрета и индивидуален для пары идентичностей устройства и участника. PSK identity - непрозрачный идентификатор ключа; запись доверия связывает его с deviceId, principalId, ролью, областью и поколением ключа. Оба Finished проверяются до protocol.hello и предметных действий. PSK-only, односторонняя аутентификация и общий ключ парка не соответствуют ядру. Самостоятельная реализация MGM не заменяет согласование TLS, TLSTREE, проверку открытых точек и управление счетчиками."
        },
        {
          "type": "heading",
          "id": "security-random",
          "text": "Случайные значения и временные ключи"
        },
        {
          "type": "p",
          "text": "Приватные временные ключи GC256A и случайные значения handshake получаются от криптографического генератора с обоснованным источником энтропии. MAC, IP, uptime, bootId и постоянный счетчик не заменяют случайность. При отказе генератора новый защищенный канал не устанавливается. PSK используется только по назначению выбранного профиля и не переносится в иной протокол или файл как ключ шифрования данных."
        },
        {
          "type": "heading",
          "id": "security-records",
          "text": "Ограничения записей и handshake"
        },
        {
          "type": "p",
          "text": "Обе стороны ДОЛЖНЫ согласовать record_size_limit=512 по RFC 8449; отсутствие расширения либо иное значение отклоняется. Для TLS 1.3 это длина TLSInnerPlaintext, включая content type и padding, но без заголовка и метки MGM. Без padding запись несет до 511 прикладных байт. Рамка uint32 BE + XML длиной 1024 байта содержит 1028 байт и требует как минимум трех защищенных записей при максимальном заполнении. Число TCP-сегментов из этого не следует. Граница записи может проходить внутри поля длины или XML-атрибута."
        },
        {
          "type": "p",
          "text": "record_size_limit не ограничивает незашифрованные сообщения начального handshake. Приемник ядра ограничивает TLSPlaintext.fragment незашифрованной записи и каждое сообщение ClientHello/ServerHello/HelloRetryRequest с его четырехбайтовым handshake-заголовком величиной 4096 байт; проверка размера выполняется до выделения буфера. Handshake разбирается порционно с сохранением управления ОУ и обслуживания локальных таймеров. После проверки MGM в прикладной парсер поступают только аутентифицированные байты; ошибка метки закрывает канал, не выдает частичный XML и не продлевает lease владельца."
        },
        {
          "type": "p",
          "text": "Ядро запрещает 0-RTT и TLS session tickets/resumption: каждое новое подключение проходит полный PSK-DHE handshake и текущую проверку допуска. Полный handshake ограничен 20000 мс. Два дорогих контекста PSK-DHE выделяются только после проверки PSK binder; неаутентифицированный TCP-клиент не занимает эти контексты. До проверки binder применяется отдельный ограниченный прием ClientHello по правилам ниже. Существующий владелец, его heartbeat и session.stop обслуживаются независимо от этой очереди."
        },
        {
          "type": "heading",
          "id": "security-rotation",
          "text": "Ротация и отзыв ключей"
        },
        {
          "type": "p",
          "text": "Ключ проходит состояния staged, active и revoked. Участник с правом управления доверием подготавливает индивидуальный новый ключ по доверенному сервисному пути; обе стороны подтверждают его установку отдельным пробным handshake до отзыва старого. В записи доверия хранятся поколение, состояние и срок разрешенного перекрытия. Отзыв и старший номер поколения атомарно записываются в энергонезависимую память до отправки подтверждения операции и сохраняются без срока, в том числе после подтверждения и reboot; откат к старой резервной копии не восстанавливает отозванное поколение. Аварийное восстановление доступа выполняется местной авторизованной процедурой с аудитом."
        },
        {
          "type": "p",
          "text": "Ключ staged допускает только пробный handshake и protocol.hello для подтверждения идентичности; управляющие и сервисные команды до перехода active запрещены. Активация требует полномочий действующего администратора доверия либо местной процедуры восстановления. Отозванный ключ не может подтвердить собственную замену."
        },
        {
          "type": "p",
          "text": "После применения отзыва УУ ДОЛЖЕН отклонять новые handshake этим ключом, закрыть его действующие соединения и аннулировать связанные права до следующей изменяющей команды. Отзыв полномочий, на которых основано текущее владение, немедленно прекращает действие его lease и применяет сохраненную stop/finish. Это самостоятельное событие, отличное от закрытия канала по таймауту; ожидание остатка lease после отзыва запрещено. Политика finish продолжает только уже принятое конечное исполнение. Отзыв роли не передает сеанс другому участнику; отозванные полномочия не допускают resume. Ротация ключа того же principalId требует нового handshake и resume; TLS KeyUpdate не заменяет ротацию идентичности."
        },
        {
          "type": "p",
          "text": "При плановой ротации без потери управления новый active-ключ того же principalId ДОЛЖЕН успешно пройти handshake, затем единственный писатель закрывает прежний управляющий канал и выполняет resume до отзыва старого. После атомарного прикрепления нового канала отзыв только прежнего ключа не аннулирует владение, уже основанное на новом действующем ключе. Отзыв самого права управления principalId действует независимо от ключа. Эта последовательность не отменяет требований к единственному активному экземпляру АРМ."
        },
        {
          "type": "heading",
          "id": "trust-provisioning",
          "text": "Первичное доверие и ключи издателя образов"
        },
        {
          "type": "p",
          "text": "Чистое УУ с securityPolicy=optional допускает открытый режим по разделу 12. При secureOnly отсутствие доверия запрещает сетевые изменяющие команды и session.start. Локальная авторизованная процедура при подтвержденном off фиксирует deviceId и открытый ключ начального администратора доверия; оператор проверяет его отпечаток (хеш Стрибог-256 от 65 байт 04 || x[32] || y[32], координаты big-endian; результат 32 байта, 64 строчных hex) по независимому доверенному источнику. Подпись, содержащаяся рядом с неизвестным ключом, не заменяет эту проверку. Общий заводской пароль или общий PSK парка не используется. Способ физического доступа и проверки личности оператора указывается в паспорте изделия; сетевой профиль не создает доверие из недоверенного канала."
        },
        {
          "type": "p",
          "text": "Переносимая запись доверия имеет формат open-alert-trust/2 по trust-schema текущей редакции: payload содержит deviceId, generation, principals, firmwareKeys; envelope содержит payload, authorityKeyId, signature. Подписывается ровно JCS(payload) в UTF-8 без BOM и завершающего перевода строки, без полей envelope. authorityKeyId выбирает заранее закрепленный ключ администратора GC256A; неизвестный ключ не становится доверенным из подписи. Алгоритм — ГОСТ Р 34.10-2012 с кривой GC256A и хешем Стрибог-256. signature содержит 128 строчных hex от r[32] little-endian || s[32] little-endian по единому правилу подписи ниже; DER и автоматическое распознавание кодировки запрещены. generation положителен и строго больше сохраненного; импорт с другим deviceId запрещен. Пакет содержит только открытые параметры и права: PSK, корневые секреты и закрытые ключи в нем запрещены. Подпись обеспечивает подлинность и целостность, но не конфиденциальность состава реестра. Сетевые config.stage и firmware.begin не импортируют доверие."
        },
        {
          "type": "p",
          "text": "principal содержит principalId, keyId, generation, state=staged|active|revoked, roles и resourceIds; для staged/active обязательны rootId и salt (64 строчных hex), для revoked они отсутствуют. rootId выбирает отдельно установленный 256-битный корневой секрет Kpair именно пары deviceId/principalId. Ключи разных пар генерируются независимо криптографическим генератором; общий ключ парка и вывод всех пар из доступного участникам общего секрета запрещены. Роли observer/operator/maintainer/trustAdmin независимы; trustAdmin не разрешает session.start. resourceIds — точный непустой список локальных ресурсов, без wildcard. keyId уникален в хранилище УУ, не переиспользуется и служит TLS PSK identity: ровно 1..32 ASCII-байта token, без завершающего NUL, префикса, hex-декодирования или Unicode-нормализации; регистр значим. Поле identity имеет стандартную длину uint16 BE из TLS 1.3, которая не входит в эти байты; obfuscated_ticket_age=0 для внешнего PSK. ClientHello содержит ровно одну identity. Пробный handshake staged не разрешает управление; активация оформляется новой подписанной редакцией."
        },
        {
          "type": "p",
          "text": "firmwareKey содержит keyId, generation, state, curve=GC256A, x, y, deviceKinds и hardwareRevisions. x/y - по 64 строчных hex, целые big-endian; проверяются принадлежность кривой, ненулевая точка и порядок подгруппы. Ключ ограничен явно перечисленными типами и ревизиями аппаратуры. keyId прошивки относится только к этому пространству издателей и не является TLS identity. staged допускает проверку пробного образа, active допускает verify/commit, revoked запрещает новый begin/verify/commit. Право обслуживания и знание PSK не дают права подписать образ или добавить издателя."
        },
        {
          "type": "p",
          "text": "Импорт доверия проверяет подпись закрепленного администратора, схему, ссылки, поколения и права; фиксируется атомарно с чтением для проверки до ответа об успехе. Прежние keyId и старшие поколения сохраняются как запреты возврата, даже если отсутствуют в новом пакете; неотозванные записи запрещено молча удалять. Смена материала под прежним keyId запрещена. Переходы staged→active→revoked и staged→revoked необратимы; новая пара ключей получает новый keyId. Обрыв питания оставляет целиком прежнее либо целиком новое состояние. Отзыв издателя перепроверяется непосредственно перед commit и при активации после reboot; проверенный ранее образ не обходит отзыв. Отзыв не выключает уже работающий образ сам по себе; последующая загрузка и восстановление используют только разрешенный образ и сохраненный минимальный номер версии."
        },
        {
          "type": "p",
          "text": "Ротация издателя включает импорт нового staged-ключа, проверку пробного подписанного образа, активацию отдельным пакетом и последующий отзыв старого. До отзыва обеспечивается разрешенный загрузочный и восстановительный образ. Замена корня администратора и восстановление после утраты всех ключей требуют той же локальной проверки независимого отпечатка, подтвержденного off и аудита; старшие версии, запреты и идентичность не сбрасываются. Аудит содержит поколения, keyId, отпечатки, инициатора и результат, но не PSK и приватные ключи."
        },
        {
          "type": "p",
          "text": "Канонизация пакета доверия: principals и firmwareKeys сортируются по keyId, roles/resourceIds/deviceKinds/hardwareRevisions - лексикографически, повторы запрещены. generation каждой записи положителен и при изменении записи строго увеличивается. Сохраненный материал revoked-ключа издателя остается в журнале отзыва; производный PSK стирается. Отозванный keyId НЕ ДОПУСКАЕТСЯ независимо от возможности повторно вычислить его PSK. rootId/salt/principalId под прежним keyId неизменны; изменение прав и состояния увеличивает generation записи, но не меняет производный ключ. Схема проверяет форму, предметная проверка - диапазон uint64, ссылочную целостность, переходы и подпись. Полномочия trustAdmin разрешают импорт уже подписанного закрепленным корнем пакета, но не заменяют его подпись."
        },
        {
          "type": "p",
          "text": "Kpair устанавливается отдельно от публичного пакета и общей прошивки в защищенное хранилище обоих участников. На чистом УУ первичная установка требует физически контролируемой авторизованной процедуры при off и независимой проверки deviceId/principalId/rootId; обычный сетевой интерфейс первоначальные секреты не принимает. Формат записи для локального импортера: ASCII OAK1 (4 байта), deviceId (16 байт из hex), principalId (16), длина rootId (uint8, 1..32), rootId (ASCII), Kpair (32 байта); всего 70..101 байт, без дополнений. Это ввод через локальный защищенный интерфейс, не незашифрованный переносимый файл. Импортер проверяет полную длину, отсутствие прежнего rootId и соответствие выбранному устройству, сохраняет атомарно и стирает временный буфер. Подтверждение выдается после чтения записи и пробного TLS handshake; обрыв оставляет прежнее либо полное новое состояние. Хранение этой записи в публичном реестре, журнале, общей прошивке или открытом экспортном файле запрещено."
        },
        {
          "type": "p",
          "text": "PSK пары вычисляется по KDF_GOSTR3411_2012_256 из RFC 7836, пункт 4.5. label — ASCII OpenAlert/TLS-PSK/1 без нуля в конце; seed — Стрибог-256 от JCS объекта с ровно пятью строковыми полями deviceId, principalId, keyId, rootId, salt. Вход HMAC: 0x01 || label || 0x00 || seed[32] || 0x01 || 0x00; ключ HMAC — Kpair, результат — 32 байта PSK. Никакого AES, SHA-256 или парольного материала в этом выводе нет. salt — новые 32 случайных байта для нового keyId. Права и generation записи не входят в seed, поэтому переход staged в active не меняет проверенный ключ. Направления и рабочие ключи соединения выводит только TLS; Kpair не используется как TLS PSK или ключ шифрования файла."
        },
        {
          "type": "p",
          "text": "Компрометация публичного trust-пакета не раскрывает PSK. Компрометация одного Kpair ограничивается данной парой, но раскрывает ее производные PSK: для восстановления заменяются Kpair/rootId и все затронутые keyId с отзывом старых. Простая смена salt при украденном Kpair не восстанавливает доверие. Корневые секреты хранятся отдельно от открытого реестра с индивидуальными правами доступа и защищенными резервными копиями; хранилище, имеющее доступ ко всем секретам, остается отдельной критичной частью системы. Невыгружаемые ключи и аппаратное хранилище допустимы, но не подменяют разграничение пар."
        },
        {
          "type": "heading",
          "id": "handshake-admission",
          "text": "Допуск соединений до выделения дорогого контекста"
        },
        {
          "type": "p",
          "text": "Прием TCP/ClientHello и выполнение PSK-DHE имеют разные квоты. Для начального приема выделены четыре короткоживущих места; каждое принимает не более 4096 байт ClientHello, без выделения ECDHE. Первый полный ClientHello ДОЛЖЕН поступить за 3000 мс от приема TCP, вся предварительная проверка — за 8000 мс. Истечение срока закрывает только этот сокет. При заполнении четырех мест новое соединение отклоняется; непрерывное поступление чужих соединений не продлевает сроки занятых мест. TCP SYN-очередь имеет отдельный конечный предел и защиту от полуоткрытых соединений; ее память не отнимается у действующего управления."
        },
        {
          "type": "p",
          "text": "ClientHello содержит ровно одну PSK identity. До ECDHE УУ проверяет структуру, разрешенный keyId и binder по RFC 8446. Затем УУ обязательно посылает HelloRetryRequest с новым случайным cookie из 32 байт на этом соединении, без изменения выбранной группы. Сохраняются cookie и хеш первого ClientHello для стандартного transcript message_hash; повторный ClientHello и его binder проверяются по полному транскрипту с HelloRetryRequest. Второй HelloRetryRequest запрещен. Cookie принимается только на выдавшем его TCP-соединении, один раз, до исходного срока 8000 мс; закрытие стирает контекст. Повтор записанного первого ClientHello или знание публичной identity не доказывают право занять дорогой слот. Cookie не заменяет binder и Finished."
        },
        {
          "type": "p",
          "text": "После успешной проверки повторного binder выделяется один из двух контекстов PSK-DHE. Пока существует владение, один контекст зарезервирован для текущего principalId, второй — для остальных допущенных участников; посторонний участник не занимает резерв. При отсутствии владения оба доступны допущенным участникам в порядке завершения предварительной проверки. На principalId ДОПУСКАЕТСЯ не более одного незавершенного дорогого контекста, включая все его keyId и TCP-адреса; второй запрос того же участника не занимает другой слот и не вытесняет первый. Полный handshake завершается не позже 20000 мс от приема TCP. При ожидании первого байта также действует срок первого ClientHello 3000 мс; ни одна фаза не начинает общий срок заново. Существующие TLS-соединения, медиа, bulk, взаимодействие с ОУ и таймеры session.stop имеют отдельные ресурсы и продолжают обслуживаться; отказ выделения контекста не отзывает владельца. Отзыв ключа проверяется повторно перед Finished и допуском команд."
        },
        {
          "type": "p",
          "text": "Приемка воспроизводит два молчащих TCP-клиента, медленный ClientHello, неверный binder, повтор захваченного ClientHello и отказ генератора случайных чисел. Два молчащих клиента не блокируют корректное подключение владельца через оставшиеся места. Дополнительно измеряются пределы частоты проверок binder, памяти и восстановления при нагрузке, заявленной для изделия. Конечный контроллер не гарантирует доступность при насыщении канала или исчерпании всех мест непрерывным потоком атакующих подключений; защита периметра сохраняется. Эти ограничения не оправдывают прежнее занятие двух дорогих слотов молчащими клиентами на 10 секунд."
        },
        {
          "type": "p",
          "text": "Прием ClientHello на канале от 64 кбит/с допускает до 3000 мс от приема TCP до полного сообщения, не более 4096 байт; весь предварительный обмен с cookie завершается за 8000 мс. Дорогие контексты по-прежнему выделяются только после binder и свежего cookie. Полный handshake завершается за 20000 мс от приема TCP. Четыре ограниченных приемных места и резерв дорогого контекста владельца не гарантируют доступность при насыщении входного канала; размеры, очередь и сроки всех фаз проверяются раздельно. Повторы подключения имеют случайную задержку по правилам backoff и не вытесняют действующее управление."
        },
        {
          "type": "heading",
          "id": "security-modes",
          "text": "Явный выбор открытого и защищенного обмена"
        },
        {
          "type": "table",
          "headers": [
            "Настройка устройства",
            "Допустимое поведение"
          ],
          "rows": [
            [
              "securityPolicy=optional",
              "Разрешены явно выбранные клиентом TLS и открытый TCP. Наличие ключей не запрещает открытый канал. Чистое устройство без ключей работает по открытому TCP с локально заданными правами."
            ],
            [
              "securityPolicy=secureOnly",
              "Разрешен только TLS. При отсутствии действующих ключей устройство не принимает сетевое управление и сообщает неготовность локально; переход на открытый TCP запрещен."
            ]
          ]
        },
        {
          "type": "p",
          "text": "В новом пустом хранилище securityPolicy=optional. Настройка «Только защищенный канал» устанавливается при местном вводе; ее отключение ДОПУСКАЕТСЯ только местно при подтвержденном off. Изменение атомарно; переход в secureOnly ДОПУСКАЕТСЯ после успешного пробного TLS с active-ключом, отзывает открытые привязки и применяет их сохраненную политику потери управления. Сброс ключей не сбрасывает secureOnly. Команда открытого канала не меняет эту настройку и не устанавливает доверие."
        },
        {
          "type": "p",
          "text": "В АРМ, конфигураторе, эмуляторе и прошивке режим соединения выбирается явно: tls либо plain. В АРМ это настройка устройства Открытый протокол систем оповещения «Защищенный обмен»: при выключении поля ключей не требуются; состояние показывает «Открытый канал». Настройка УУ «Только защищенный канал» имеет приоритет над выбором клиента. Ошибка ключа, TLS или таймаут не вызывают автоматического повторения через plain. Открытый режим поддерживает обычные команды управления, оповещения и чтения в пределах локальных прав; он не является только диагностическим режимом."
        },
        {
          "type": "p",
          "text": "Один TCP-порт может принимать оба режима. До разбора первого сообщения соединение имеет состояние undecided: первый байт 0x16 выбирает TLS, 0x00 — открытую четырехбайтовую рамку XML длиной 1..1024; любое другое значение закрывает соединение. При secureOnly вариант 0x00 немедленно отклоняется. Выбор закрепляется на срок соединения: неверный TLS никогда не передается XML-парсеру и наоборот. Возможны отдельные явно настроенные порты с теми же правилами. Открытый клиент сразу передает protocol.hello; защищенный — после завершенного TLS. Оба используют один wire-профиль и размеры XML."
        },
        {
          "type": "p",
          "text": "Открытый канал не проверяет PSK и не доказывает личность отправителя. В protocol.hello его элемент hello содержит обязательный clientId (32 строчных hex), постоянно сохраняемый клиентом; при tls этот атрибут запрещен. УУ использует пару plain/clientId как логическую идентичность открытого клиента, чтобы разделять последовательности и повторы, но не как доказательство полномочий. Все открытые клиенты получают одни локально настроенные plainRoles/plainResourceIds; clientId не выбирает привилегированную учетную запись. В пустой конфигурации роли observer/operator, область — явно объявленные локальные ресурсы; реальное оповещение разрешено. АРМ не меняет clientId при обычном переподключении. Кто скопировал clientId, неотличим от прежнего открытого клиента; это ограничение plain, а не защищенная идентификация. IP-фильтр также не объявляется криптографической аутентификацией. Проверки эпохи, привязки, повторов, занятости, ресурсов и физического результата сохраняются."
        },
        {
          "type": "p",
          "text": "Требования о криптографически аутентифицированном principalId относятся к tls. Для plain вместо него используется явно обозначенная логическая идентичность plain/clientId с описанной границей доверия. Контекст владения и mediaId/transferId фиксирует также режим; открытый канал не может resume, heartbeat, session.stop или продолжить операцию, принятую от TLS-участника. Переход между режимами требует завершения прежнего владения и нового acquire. Это разделение не ограничивает местную аварийную остановку и не делает открытое управление защищенным."
        },
        {
          "type": "p",
          "text": "device.describe сообщает device.securityPolicy=optional|secureOnly. Ответ protocol.hello сообщает hello.security=tls|plain; клиент сверяет его с фактическим каналом. Режим показывается в диагностике и в журнале соединения. Для аудио и bulk применяется тот же выбор: управление tls требует TLS на связанных каналах; управление plain допускает только явно подготовленный открытый TCP. media.hello/transfer.hello сохраняют привязку контекста, но в plain не удостоверяют источник криптографически. UDP и multicast не входят в базовый rtp-alert/4; они определены отдельным дополнительным групповым профилем. Подпись образа прошивки и проверка ее издателя независимы от шифрования транспорта и сохраняются в открытом режиме."
        },
        {
          "type": "p",
          "text": "Режим без ключей является разрешенным эксплуатационным выбором, а не обещанием защиты от подмены. Для tls сохраняются запреты отключения криптографии и все требования TLS-профиля. Проверки открытого режима включают работу чистого устройства, отсутствие запросов ключа в АРМ, отказ plain при secureOnly, отсутствие автоматического понижения защиты и невозможность вмешательства plain в TLS-владение. Изменение касается только Открытый протокол систем оповещения; другие продуктовые протоколы сохраняют свои правила."
        },
        {
          "type": "heading",
          "id": "commissioning-sequence",
          "text": "Первичный ввод и первое подключение"
        },
        {
          "type": "p",
          "text": "Настоящий порядок объединяет правила первичного доверия, open-alert-trust/2 и защищенного соединения для совместимых изделий любых изготовителей. Он применяется при выборе tls. Явно выбранный plain при securityPolicy=optional допускает работу без выдачи ключей; регистрация открытого клиента не устанавливает криптографическое доверие. Первая запись прошивки, внесение доверия и регистрация в АРМ являются отдельными действиями, даже если сервисный инструмент выполняет их в одном задании."
        },
        {
          "type": "p",
          "text": "Администратор доверия определяет допущенных участников, их роли и ресурсы, утверждает подписанные пакеты доверия. Уполномоченный установщик выполняет местные действия и проверяет соответствие выбранному устройству. АРМ и конфигуратор действуют от собственных principalId и только с назначенными правами. Совмещение функций одним инструментом ДОПУСКАЕТСЯ при сохранении этих границ; роль trustAdmin сама по себе не разрешает управление оповещением. Изготовитель определяет физический способ доступа, проверку полномочий установщика и защищенный путь внесения данных в паспорте изделия."
        },
        {
          "type": "p",
          "text": "Шаг 1. Установщик устанавливает соответствие физического устройства его deviceId и проверяет подтвержденный off перед местным внесением доверия. При неизвестном состоянии выхода применяется процедура восстановления устройства; неизвестное состояние не считается выключением. Способ первичного программирования и начальное состояние хранилища определяются паспортом изделия; общее программное обеспечение не содержит индивидуальных секретов. Обычное обновление программы не создает новую идентичность и не сбрасывает сохраненные запреты возврата."
        },
        {
          "type": "p",
          "text": "Шаг 2. Местная авторизованная процедура закрепляет открытый ключ администратора доверия. Установщик сверяет его отпечаток Стрибог-256 по независимому доверенному источнику согласно правилам первичного доверия. Ключ из обнаруженного сетевого объявления или из пакета с подписью этого же неизвестного ключа не принимается как основание доверия. Ключ администратора, ключ издателя прошивки и секрет пары выполняют разные функции; полномочие одного не подменяет остальные."
        },
        {
          "type": "p",
          "text": "Шаг 3. Для выбранных deviceId и principalId создается независимый случайный Kpair длиной 256 бит и назначается rootId. Защищенный контур ввода использует криптографический генератор и устанавливает секрет в защищенные хранилища обоих участников. Локальный импортер получает запись OAK1 установленного формата, проверяет идентификаторы и полную длину, атомарно сохраняет данные и стирает временный буфер. Обычный сетевой интерфейс не принимает первоначальный секрет. rootId, уже присутствующий в хранилище, не используется для перезаписи другим материалом. Завершение записи на одной стороне не означает завершения выдачи паре."
        },
        {
          "type": "p",
          "text": "Шаг 4. Администратор выпускает подписанный пакет open-alert-trust/2 с новым keyId, новым случайным salt, состоянием staged, согласованными rootId и principalId, ролями и точным списком ресурсов. УУ проверяет закрепленного подписанта, deviceId, поколения, схему и связи, затем атомарно фиксирует пакет. Клиент использует соответствующую запись той же пары. Стороны выводят PSK по установленному KDF; секреты не включаются в пакет доверия. Подготовленная запись не дает права управления. Сохранение Kpair и применение staged-записи предшествуют пробному соединению; подтверждение завершенной установки выдается после этого соединения, как установлено для локального импортера."
        },
        {
          "type": "p",
          "text": "Шаг 5. Клиент явно выбирает tls и выполняет полный handshake с подготовленным keyId. Обе стороны проверяют Finished; затем клиент передает protocol.hello профиля bootstrap/1 и сверяет deviceId, selectedProfile, bootId и security=tls с выбранным устройством и фактическим каналом. Пробное соединение с staged-ключом допускает только handshake и protocol.hello; acquire, session.start и сервисные команды не используются для проверки ключа. Подтверждение относится к конкретным deviceId, principalId, keyId, rootId и salt. Неверная идентичность, несовместимый профиль или ошибка защиты прекращают процедуру без активации и без перехода на plain."
        },
        {
          "type": "p",
          "text": "Шаг 6. После успешного пробного соединения уполномоченный администратор оформляет переход staged в active новой подписанной редакцией пакета и увеличением поколения измененной записи. keyId, rootId, salt и principalId сохраняются; переход состояния не меняет выведенный PSK. Факт успешного handshake сам по себе не изменяет запись доверия. Клиент устанавливает новое полное TLS-соединение действующим ключом и повторяет protocol.hello. Продолжение пробного соединения не заменяет проверку действующего допуска."
        },
        {
          "type": "p",
          "text": "Шаг 7. Через рабочее соединение АРМ читает device.describe, device.capabilities и device.state в пределах назначенных прав, сверяет идентичность, тип, ресурсы, ревизии и ограничения. Регистрация связывает логическую цель с конкретным deviceId, resourceId и bindingGeneration по правилам раздела регистрации. Успех подключения не запускает ОУ и не является подтверждением готовности всех его физических функций. Для управления отдельно выполняются проверки прав, ресурсов и acquire; право прерывания сохраняет установленную связь с происхождением запуска."
        },
        {
          "type": "p",
          "text": "Если выбран режим \"Только защищенный канал\", местная процедура устанавливает secureOnly после успешного пробного соединения с active-ключом по правилам явного выбора транспорта. Наличие ключа само по себе не меняет optional на secureOnly. Применение secureOnly аннулирует открытые привязки с их сохраненной политикой потери управления. Итог ввода различает записанный секрет, подготовленный ключ, активный ключ и установленное рабочее соединение; это результаты процедуры, а не новые состояния сетевого протокола."
        },
        {
          "type": "heading",
          "id": "commissioning-storage",
          "text": "Хранение и учет выдачи ключей"
        },
        {
          "type": "p",
          "text": "Для другого principalId на том же устройстве и для другого устройства у того же участника создаются независимые секреты пар. Общая учетная запись или резервный экземпляр АРМ не отменяют требования единственного активного экземпляра участника. Передача полномочий другому участнику выполняется новой регистрацией и назначением прав; копирование чужой записи Kpair не заменяет эту процедуру."
        },
        {
          "type": "p",
          "text": "Хранение и перенос секретов выполняются средствами защиты, установленными разделом безопасности, включая защищенные резервные копии. OAK1 является записью локального импортера и не становится форматом открытого переносимого файла. Секреты исключаются из общего образа прошивки, публичного пакета доверия, обычного экспорта конфигурации и журналов. Учет выдачи содержит идентификаторы участников и ключей, поколения, отпечатки открытых ключей, инициатора и результат; значения Kpair, PSK и закрытых ключей в учет не включаются."
        },
        {
          "type": "heading",
          "id": "commissioning-retry",
          "text": "Продолжение процедуры после отказа"
        },
        {
          "type": "p",
          "text": "После обрыва связи или питания установщик сначала устанавливает фактически сохраненные идентичности, поколения и состояния через авторизованный местный интерфейс либо доступный действующий сервисный путь. Новое доверие не выводится из прежнего сообщения об успехе или из наличия файла на компьютере. В пределах одного хранилища сохраняется прежняя либо полная новая запись; атомарность двух раздельных участников не предполагается."
        },
        {
          "type": "p",
          "text": "Если секрет записан только на одной стороне, завершается внесение той же пары на вторую сторону через защищенный путь. Уже сохраненный rootId не перезаписывается повторным импортом OAK1. Если соответствующий секрет недоступен либо его происхождение вызывает сомнение, применяются отзыв и местное восстановление с новым Kpair/rootId; случайная замена секрета под прежними идентификаторами запрещена. Незавершенная выдача не дает готовности защищенного соединения."
        },
        {
          "type": "p",
          "text": "Если сохранен staged, повторяется пробное соединение с этой записью; ошибка или таймаут не переводят ее в active. Если подтверждение применения active потеряно, проверяется сохраненное состояние и устанавливается новое соединение с тем же ключом. Уже примененная редакция не выдается за новое изменение; пакет со старым поколением не обходит запрет отката. Отзыв остается действующим и при потере подтверждения. Отказ генератора случайных чисел прекращает создание ключа или нового защищенного соединения без подстановки предсказуемого материала."
        },
        {
          "type": "heading",
          "id": "commissioning-rotation",
          "text": "Плановая замена ключа"
        },
        {
          "type": "p",
          "text": "При плановой замене без компрометации для той же пары ДОПУСКАЕТСЯ использовать действующий Kpair: назначаются новые keyId и salt, затем выполняются подготовка staged, пробное соединение и активация. Старый ключ остается действующим только в разрешенном интервале перекрытия. Изменение generation или TLS KeyUpdate само по себе не является заменой ключа участника."
        },
        {
          "type": "p",
          "text": "При действующем владении новый active-ключ того же principalId проходит handshake. Единственный активный экземпляр АРМ закрывает прежний управляющий канал, выполняет resume и подтверждает прикрепление к существующей эпохе до отзыва старого ключа. Новый session.start вместо resume запрещен. Отзыв только прежнего ключа после успешного прикрепления не отзывает владение, основанное на новом ключе. Если переход не завершен, применяются обычные сроки владения и политика потери управления; наличие нового ключа не продлевает их."
        },
        {
          "type": "heading",
          "id": "commissioning-recovery",
          "text": "Отзыв, утрата ключей и замена устройства"
        },
        {
          "type": "p",
          "text": "При компрометации применяется отзыв затронутых ключей и полномочий по правилам безопасности. Отозванные ключи отвергаются, их соединения закрываются, а связанное владение обрабатывается по сохраненной политике stop/finish. Если раскрыт Kpair, меняются Kpair/rootId и все производные keyId этой пары; новая соль при прежнем секрете не восстанавливает доверие. Новое доверие не подтверждается отозванным участником."
        },
        {
          "type": "p",
          "text": "При утрате всех действующих ключей или замене корня администратора выполняется местная авторизованная процедура при подтвержденном off с независимой проверкой отпечатка и аудитом. Восстановление сохраняет deviceId, старшие поколения, отозванные идентификаторы, минимальную разрешенную версию и securityPolicy. Восстановление резервной копии не отменяет более поздний отзыв; копия с утраченными сведениями о старших поколениях не становится основанием допуска. Сброс секретов не выключает secureOnly. При отсутствии действующего доверия в secureOnly сетевое управление остается недоступным до завершения местного восстановления."
        },
        {
          "type": "p",
          "text": "Замена физического устройства использует его собственную идентичность и новую привязку ресурсов по разделу регистрации. Перенос совместимой конфигурации не переносит Kpair, PSK, активное владение или сеанс прежнего устройства. Совпадение IP-адреса не подтверждает замену и не позволяет принять ответ нового устройства за результат прежнего запуска. Восстановленный либо новый экземпляр АРМ также соблюдает запрет вытеснения действующего владельца."
        },
        {
          "type": "heading",
          "id": "commissioning-passport",
          "text": "Параметры процедуры в паспорте изделия"
        },
        {
          "type": "p",
          "text": "Паспорт изделия ДОЛЖЕН конкретизировать способы выполнения установленной процедуры. Эти сведения определяют местный сервисный путь; они не вводят общие для всех изготовителей разъем, программатор, загрузчик или интерфейс приложения. Паспорт указывает:"
        },
        {
          "type": "list",
          "items": [
            "способ первичного программирования, местного входа в сервисный режим, проверки полномочий установщика и подтверждения off;",
            "способ независимой проверки deviceId и отпечатка администратора, назначения principalId и области ресурсов;",
            "защищенный путь импорта OAK1 и подписанного пакета доверия, средства хранения и переноса секретов, источник случайности;",
            "способ чтения сохраненных поколений и состояний без выдачи секретов, признаки завершения каждого шага и порядок продолжения после отказа;",
            "порядок резервирования, плановой замены, отзыва и местного восстановления с сохранением запретов возврата;",
            "место хранения аудита, состав доступных оператору результатов и способ явного выбора optional либо secureOnly."
          ]
        }
      ]
    },
    {
      "id": "media",
      "title": "13. Речь, записи и время",
      "blocks": [
        {
          "type": "note",
          "text": "Речь передается локальной записью, потоком записи либо микрофоном. Для сети применяется rtp-alert/4 настоящего раздела; для автономного последовательного оповещения - mode=program раздела 24. Управление всеми режимами использует явно выбранный tls либо plain, точные ресурсы и общие критерии физического результата."
        },
        {
          "type": "heading",
          "id": "media-sources",
          "text": "Три способа передачи речи"
        },
        {
          "type": "table",
          "headers": [
            "Способ оповещения",
            "Где находится источник",
            "Необходимый контракт"
          ],
          "rows": [
            [
              "Локальная запись",
              "Полное проверенное содержимое доступно на УУ/ОУ до session.start.",
              "Размещение и атомарная публикация содержимого, идентичность и хеш, готовность, запуск одного прохода, естественное окончание, отмена и физический результат. Передача аудио по сети во время исполнения не обязательна."
            ],
            [
              "Потоковая запись",
              "Файл хранится на АРМ и передается получателям во время исполнения.",
              "Кроме неизменного содержимого нужны доставка потока с явно выбранным режимом tls либо plain, подготовка получателей, временная шкала, ограниченный буфер, учет потерь и точная граница окончания. Шифрование звука не обязательно. Наличие файла на АРМ не делает его локальной записью УУ."
            ],
            [
              "Микрофон",
              "Отсчеты создаются разрешенным источником в реальном времени.",
              "Нужны доставка потока и право говорить: выдача, удержание, отпускание и отзыв тангенты, смена источника, предел задержки и реакция на потерю аудио отдельно от потери управления. Полного файла и хеша будущей речи до начала нет."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Локальный режим mode=recording выбирает неизменяемый объект, доступный УУ или ОУ целиком до session.start: contentId/revision/hashAlgorithm/hash. Один session.start выполняет один проход. Файл с АРМ во время исполнения передается через mode=stream и audio.prepare. Подмена локальной записи URL или сетевым источником в поле contentId запрещена."
        },
        {
          "type": "heading",
          "id": "media-local",
          "text": "Запись на стороне исполнения"
        },
        {
          "type": "table",
          "headers": [
            "Локальная запись",
            "Требование"
          ],
          "rows": [
            [
              "Размещение",
              "Производственная загрузка допустима без сетевого аудиоканала. Сетевое обновление контента использует команды раздела 13: ограниченные блоки, повтор/продолжение, проверка полного объекта, атомарное применение и восстановление после обрыва питания. Сетевая доставка использует content.begin, передачу байтов по bulk/1 и content.commit; до commit объект не доступен для запуска."
            ],
            [
              "Идентичность",
              "Манифест фиксирует точные байты, размер, кодек и параметры декодирования, длительность либо число отсчетов, неизменные contentId/revision и Стрибог-256. Хеш контейнера и хеш декодированного звука не взаимозаменяемы; объект хеширования объявляется явно."
            ],
            [
              "Готовность",
              "До воздействия проверены доступность всей записи, ее идентичность, целостность и возможность исполнения на выбранных ресурсах. Полный хеш может быть заранее вычислен при размещении; session.start проверяет актуальность сохраненного доказательства и неизменность объекта. Номер слота или общий режим ОУ не доказывает выбранное содержимое."
            ],
            [
              "Естественный конец",
              "Один проход завершается только по объявленному критерию окончания именно этой записи. Истечение maxDurationMs, возврат общего idle без достаточного контекста и отсутствие связи не являются доказательством полного воспроизведения."
            ],
            [
              "Замена и повтор",
              "Принятый сеанс удерживает неизменную версию до конца. Замена слота не меняет исполняемый объект. Новый проход является новым действием; повтор сетевой команды не начинает воспроизведение сначала."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "media-contract",
          "text": "Контракт потока и параметры аудио"
        },
        {
          "type": "table",
          "headers": [
            "Часть потокового профиля",
            "Обязательное требование"
          ],
          "rows": [
            [
              "Формат аудио",
              "Точный кодек и вариант, частота, каналы, число отсчетов в пакете, разрешенный последний неполный пакет, порядок байтов и битов, эталонные байты кодирования. Изменение параметров посреди потока без нового согласования запрещено."
            ],
            [
              "Заголовок и шкала",
              "Побайтовая схема заголовка, длины, номера пакетов и отсчетов, начальные значения, поведение при переполнении, поле полезной длины, границы сообщения и максимальный пакет с защитой. Позиция отсчета задает порядок воспроизведения, а не адрес записи в память устройства."
            ],
            [
              "Доставка",
              "Один точный набор транспорта и защиты на profileId; направление установления связи, адреса/порты, unicast либо multicast, подготовка каждого получателя и правила маршрутизации. Формулировка \"UDP/RTP\" не выбирает такой набор."
            ],
            [
              "Разрешение потока",
              "Защищенное управление связывает поток с bootId, controlEpoch, sessionId, ресурсами, разрешенным источником и mediaId. До помещения в воспроизводимый буфер проверяются разрешенный контекст, свежесть, окно повторов и условия допуска источника выбранного профиля. В режиме tls проверка MGM обязательна до приема пакета. В явно выбранном plain источник криптографически не удостоверяется; этот режим не объявляется защищенным. IP, порт и SSRC сами по себе не доказывают подлинность отправителя."
            ],
            [
              "Начало",
              "Условия готовности тракта, срок первого пригодного пакета, порог стартового буфера, правило запуска воспроизведения и предельная задержка. Первое пришедшее сообщение не назначает источник, контекст или параметры потока. Готовность каждого получателя подтверждается отдельно."
            ],
            [
              "Буфер",
              "Начальный и максимальный объем в отсчетах и миллисекундах, окно перестановки, срок опоздания, поведение при переполнении и underrun. Дубликаты не воспроизводятся повторно; поздний пакет не вставляется в уже выданный участок. Отставание не накапливается без предела."
            ],
            [
              "Потери",
              "Политика восстановления до срока воспроизведения, допустимость повторной передачи/FEC, замены пропуска тишиной или алгоритмом маскирования. Подмененные/пропущенные отсчеты учитываются в результате; восстановление прошлого участка не вызывает позднего повторного звучания."
            ],
            [
              "Окончание",
              "Конечная граница в той же шкале отсчетов передается по управляющему и медиаканалу выбранного режима; в tls оба аутентифицированы, в plain криптографической проверки нет. Определяются подтверждение ее принятия, идемпотентный повтор, ограниченный срок дренирования и отсутствие новых данных после границы. Потеря источника или таймаут не подменяются штатным EOF."
            ],
            [
              "Остановка",
              "Адресная отмена отзывает поток, запрещает новые пакеты и отменяет отложенные действия, очищает очереди до аппаратного вывода. Неотменяемый остаток аппаратного буфера входит в измеренный бюджет session.stop; очистка одной сетевой очереди недостаточна."
            ],
            [
              "Результат",
              "Состояние тракта, принятые и выданные в тракт отсчеты, пропуски/подстановки, принятая граница конца, причины отказа и физические наблюдения для каждого получателя. Счетчик отправленных АРМ байтов не является результатом воспроизведения."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Базовый вариант rtp-alert/4 - G.711 A-law, 8000 Гц, mono, 160 отсчетов на пакет: 20 мс, 50 пакетов/с, 64 кбит/с полезного звука. Дополнительный вариант GSM использует пакетизацию 20, 40 или 80 мс. A-law и mu-law различаются; mu-law в rtp-alert/4 не применяется. Payload type и формат кодеков определены RFC 3551. Собственный четырехбайтовый счетчик перед звуком не является заголовком RTP."
        },
        {
          "type": "p",
          "text": "Буфер 120 мс соответствует 960 отсчетам: 960 байтам G.711 либо 1920 байтам после декодирования в 16-битный PCM. Это только звуковые данные; сетевые очереди, заголовки, защита, метаданные, стек и буфер аппаратного вывода считаются дополнительно."
        },
        {
          "type": "p",
          "text": "Предел буфера микрофона в rtp-alert/4 равен 1000 мс, начальное накопление - 400 мс. Непрерывное отсутствие пригодного аудио 5000 мс вызывает остановку. Эти значения являются требованиями профиля; изменение чисел требует другого идентификатора медиапрофиля."
        },
        {
          "type": "heading",
          "id": "media-transport",
          "text": "Транспорт и multicast"
        },
        {
          "type": "table",
          "headers": [
            "Выбор транспорта",
            "Условие решения"
          ],
          "rows": [
            [
              "Локальное воспроизведение",
              "Запись доставляется и проверяется до session.start. Аудиотрафик во время исполнения НЕ ТРЕБУЕТСЯ."
            ],
            [
              "Речевой TCP unicast",
              "rtp-alert/4: отдельное соединение TLS при защищенном управлении; открытый TCP при явно выбранном plain. Переключение после ошибки запрещено. Разрешение, подготовка, EOF и результаты одинаковы; гарантия подлинности есть только в TLS."
            ],
            [
              "Групповая доставка и UDP",
              "Групповые профили group-audio/1 и group-audio-open/1 полностью определены в нормативном приложении Ж настоящего документа; в онлайн-версии — в разделе «Групповая передача речи». Они дополняют базовую одноадресную доставку и требуют поддержки групповой маршрутизации. Защищенный профиль проверяет подпись источника. Сам rtp-alert/4 остается одноадресным."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Общего ограничения числа УУ протокол не устанавливает. Каждая реализация объявляет проверенные квоты. Один поток для большой группы требует расчета числа соединений, ключей, пакетов и суммарной полосы источника; наличие сети Ethernet само по себе емкость не доказывает. Доверенный узел распределения является отдельной границей полномочий и учета результатов; он не превращает чужой поток в сквозное криптографическое доказательство источника."
        },
        {
          "type": "heading",
          "id": "media-bandwidth",
          "text": "Скорость сети и число получателей"
        },
        {
          "type": "p",
          "text": "Таблица ниже оценивает защищенный PCMA/8000/mono: 160 байт на 20 мс. Один пакет содержит RTP 32 Б, рамку 2 Б, расходы TLS 22 Б, TCP 20 Б, IPv4 20 Б и Ethernet 38 Б. Для ACK учитывается минимальный Ethernet-кадр с преамбулой и межкадровым интервалом: 84 Б. Расчет предполагает один ACK на пакет и один RTP в TLS-записи/TCP-сегменте, без TCP options, padding и повторов. Часы каждые 2 с добавляют 1600 бит/с на соединение с учетом двух сообщений и двух ACK. Это явно заданный расчетный сценарий, не универсальный верхний предел сети."
        },
        {
          "type": "table",
          "headers": [
            "УУ",
            "Только звук, Мбит/с",
            "Защищенные данные, Мбит/с",
            "Данные, ACK и часы, Мбит/с",
            "С резервом 30%, Мбит/с"
          ],
          "rows": [
            [
              "1",
              "0,064",
              "0,1176",
              "0,1528",
              "0,1986"
            ],
            [
              "10",
              "0,64",
              "1,176",
              "1,528",
              "1,9864"
            ],
            [
              "100",
              "6,4",
              "11,76",
              "15,28",
              "19,864"
            ],
            [
              "1 000",
              "64",
              "117,6",
              "152,8",
              "198,64"
            ],
            [
              "4 000",
              "256",
              "470,4",
              "611,2",
              "794,56"
            ],
            [
              "10 000",
              "640",
              "1 176",
              "1 528",
              "1 986,4"
            ]
          ]
        },
        {
          "type": "p",
          "text": "Формула защищенных данных: R=(P+32+2+22+20+20+38)*8*1000/T бит/с. Суммарный трафик двух направлений с принятыми ACK и часами: R+84*8*1000/T+1600. Один PCMA-получатель требует 117,6 кбит/с прямых данных и 152,8 кбит/с суммы направлений; 4000 получателей — 611,2 Мбит/с, с 30% резервом — 794,56 Мбит/с. Направления полнодуплексного интерфейса проверяются также отдельно. TCP options, VLAN, туннели, дополнительные записи TLS, реальные ACK и повторы добавляются по трассе; резерв не заменяет их измерение. В plain расходы TLS отсутствуют, но все прочие расходы и проверки сроков сохраняются."
        },
        {
          "type": "heading",
          "id": "media-slow",
          "text": "Медленные каналы"
        },
        {
          "type": "table",
          "headers": [
            "Медленный канал: вариант для сравнения",
            "Пакетизация",
            "Звук, кбит/с",
            "TLS/TCP/IP, кбит/с",
            "TLS/TCP/Ethernet, кбит/с"
          ],
          "rows": [
            [
              "G.711 A-law",
              "20 мс / 160 Б",
              "64",
              "102,4",
              "117,6"
            ],
            [
              "GSM 06.10",
              "20 мс / 33 Б",
              "13,2",
              "51,6",
              "66,8"
            ],
            [
              "GSM 06.10",
              "40 мс / 66 Б",
              "13,2",
              "32,4",
              "40"
            ],
            [
              "GSM 06.10",
              "80 мс / 132 Б",
              "13,2",
              "22,8",
              "26,6"
            ]
          ]
        },
        {
          "type": "p",
          "text": "Для суммарного канала 64 кбит/с расчетный вариант — GSM с пакетизацией 80 мс. Данные требуют 26,6 кбит/с, ACK — 8,4, часы — 1,6; сумма 36,6 кбит/с, с 30% резервом — 47,58. Для управления резервируется не менее 12 кбит/с, оставшиеся 4,42 кбит/с не считаются гарантией скрытых накладных расходов. Реальные TCP options, туннели и потери включаются в расчет допуска. Во время речи фоновые загрузки приостанавливаются либо получают только доказанно свободную полосу. Поддержка GSM/80 обязательна для реализации, объявляющей живую речь на канале 64 кбит/с; общая поддержка stream сама по себе этого не обещает."
        },
        {
          "type": "p",
          "text": "GSM 06.10 допускает 20, 40 либо 80 мс: один, два либо четыре последовательных 33-байтовых кадра RFC 3551. PCMA допускает только 20 мс. Выбор фиксируется в audio.prepare до session.start; неподдержанный вариант дает unsupported. При недостаточной полосе с учетом защиты, ACK и восстановления потерь поток не запускается. Известное сообщение можно доставить целиком и воспроизвести локально; увеличение буфера не исправляет постоянно недостаточную скорость канала."
        },
        {
          "type": "heading",
          "id": "media-latency",
          "text": "Задержка микрофона и буфер"
        },
        {
          "type": "table",
          "headers": [
            "Задержка микрофона и небольшой буфер",
            "Правило проектирования"
          ],
          "rows": [
            [
              "Цель",
              "Минимальная устойчивая задержка: накопление пакета + кодирование + доставка + проверка защиты + jitter-буфер + аппаратный вывод. Каждая составляющая измеряется; размер буфера не равен всей сквозной задержке."
            ],
            [
              "Начальная инженерная оценка",
              "Для потоковой записи выбираются разрешенные initialMs/maxMs по измеренному джиттеру. Для микрофона действуют фиксированные 400/1000 мс. Параметры согласуются до session.start и не меняются посреди потока; измеряются задержки на фактическом канале, включая 64 кбит/с."
            ],
            [
              "Адаптация",
              "Буфер ограничен initialMs/maxMs выбранного rtp-alert/4. Рост задержки для компенсации потерь запрещен; просроченные отсчеты заменяются тишиной с учетом потерь, при достижении таймаута поток останавливается."
            ],
            [
              "Восстановление потерь",
              "Буфер компенсирует джиттер и перестановку, но не восстанавливает исчезнувшие пакеты. В rtp-alert/4 нет прикладных FEC и повторной передачи RTP; TCP повторяет потерянные сегменты только в пределах допустимого возраста; поздние пакеты отбрасываются, утрата отражается в счетчиках и результате."
            ],
            [
              "Предельное условие",
              "Нулевые потери и минимальная задержка одновременно не гарантируются для произвольной сети. Приемка задает допустимые потери/джиттер и проверяет разборчивость, полноту, задержку и session.stop; выход за эту область не скрывается успешным результатом."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "media-age",
          "text": "Подлинность, возраст и очереди речевых пакетов"
        },
        {
          "type": "p",
          "text": "rtp-alert/4 принимает аудио внутри отдельного TCP unicast-соединения, привязанного через media.hello к bootId/controlEpoch/mediaId. Для управления tls этот канал обязательно защищен TLS 1.3 и аутентифицирован тем же principalId. Для явно выбранного plain используется открытый TCP без проверки ключей; криптографическая гарантия этого раздела к нему не относится. В tls метка MGM проверяется до разбора или помещения байтов в звуковую очередь. UDP, включая физически изолированную сеть, и multicast не входят в этот профиль; audio.prepare с таким маршрутом дает unsupported без выделения mediaId. Для больших групп применяется отдельный профиль group-audio/1 либо явно открытый group-audio-open/1. Групповой ключ сам по себе не удостоверяет источник перед получателями; в group-audio/1 источник доказывается отдельной подписью каждой части блока. Результаты, права и бюджеты каждого УУ сохраняются."
        },
        {
          "type": "p",
          "text": "После ready XML больше не передается. Каждая бинарная рамка содержит uint16 BE длины, затем 1..192 байта. RTP использует двухбайтовое обрамление RFC 4571; нулевая рамка keepalive в этом профиле запрещена. RTP-рамка начинается с 0x90 и содержит заголовок и данные rtp-alert/4. Служебные рамки часов начинаются с 0x00; это расширение Открытого протокола систем оповещения, а не RTP или RTCP. Неверная длина, тип или направление закрывают медиасоединение. Максимальный единичный буфер рамки вместе с длиной — 194 байта; накопление всей очереди TCP в памяти приложения НЕ ТРЕБУЕТСЯ."
        },
        {
          "type": "table",
          "headers": [
            "Бинарная рамка часов",
            "Точный формат"
          ],
          "rows": [
            [
              "Запрос УУ к источнику",
              "00 01 || challenge:uint64 BE; длина тела 10 байт. challenge начинается с 1 и возрастает без переполнения в пределах медиасоединения. Одновременно ожидается один ответ."
            ],
            [
              "Ответ источника к УУ",
              "00 02 || challenge:uint64 BE || sourceMs:uint64 BE; длина тела 18 байт. sourceMs — монотонное время источника в миллисекундах, снятое непосредственно перед постановкой ответа в очередь транспорта. Повторный или непрошенный ответ не обновляет часы."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Монотонные часы обоих участников имеют дискретность не хуже 1 мс и относительное расхождение не более 500 ppm. После ready УУ сразу отправляет запрос часов, затем повторяет его каждые 2000 мс. u0 — монотонное время УУ перед постановкой запроса в транспорт; u2 — время после получения и проверки полного ответа (в tls — также метки MGM); s1 — sourceMs из ответа. При 0 <= u2-u0 <= 1000 мс смещение часов УУ относительно источника находится в [u0-s1, u2-s1]. Интервал расширяется с каждой стороны на D=1+ceil(500*(now-u0)/1000000) мс: учтены квантование миллисекунд и относительное расхождение часов до 500 ppm. Все вычисления разностей выполняются знаковым расширенным типом, без uint64 wrap. Подтверждение действует до u0+10000 мс; новый действительный ответ заменяет прежний интервал. RTT свыше 1000 мс не освежает оценку. До первой оценки RTP не принимается; при невозможности получить ее до prepareDeadlineMs результат prepareTimeout."
        },
        {
          "type": "p",
          "text": "capturedAtMs в RTP — монотонное время источника для первого отсчета пакета, в той же шкале, что sourceMs. Микрофон ставит время фактического захвата, включая аппаратный буфер перед кодированием; поток записи формируется и датируется в реальном темпе воспроизведения. Время нельзя подменять моментом отправки задержанного пакета. Отметки не убывают; расхождение последовательности отсчетов и монотонных часов за пределами объявленных допусков является mediaClockFault. На стороне УУ консервативная верхняя оценка возраста A=now-capturedAtMs-(u0-s1-D), нижняя B=now-capturedAtMs-(u2-s1+D). Пакет ДОПУСКАЕТСЯ только при -2 <= A <= 3000 мс; A < -2 доказывает отметку из будущего даже с учетом погрешности. Отрицательное B при неотрицательном A само по себе не является отказом. Оценка проверяется после проверки выбранного режима и повторно для срока выдачи последнего действительного отсчета, включая очередь ЦАП; устаревший пакет не воспроизводится. Дополнительное ожидание TCP не продлевает срок."
        },
        {
          "type": "p",
          "text": "Просроченный пакет учитывается один раз как latePackets; при доказанной позиции невоспроизведенный участок отражается как потеря/маскирование по общим правилам. Потеря допустимой оценки часов при действующем источнике прекращает сеанс с mediaClockFault; отсутствие пригодного аудио 5000 мс — с sourceUnavailable для микрофона либо mediaIncomplete для записи. Изменение причины уже начатой остановки подчиняется общему приоритету результата. После подтвержденного END оценка остается необходимой для выдачи хвоста, но новый запрос часов не продлевает дренирование. Невыполнение срока не заменяется поздним доигрыванием."
        },
        {
          "type": "p",
          "text": "Источник ограничивает совокупную очередь еще не подтвержденных TCP-байтов пятьюдесятью звуковыми пакетами и возрастом первого отсчета 1000 мс. В границу входят приложение, TLS и сетевой стек; TCP ACK не считается доказательством воспроизведения. При невозможности соблюдать границу источник закрывает только медиасоединение и инициирует адресный session.stop через управление; удалять середину TCP-потока и продолжать прежний mediaId запрещено. Принятые УУ рамки удерживаются лишь в ограниченном буфере initialMs/maxMs; проверка возраста охватывает также аппаратный вывод. Повторное подключение медиаканала к уже использованному mediaId не возобновляет поток. Новый поток требует новой подготовки и session.start."
        },
        {
          "type": "p",
          "text": "Микрофон передает пакеты непрерывно с согласованным packetMs, включая естественные паузы речи. В паузе кодируется тишина выбранным кодеком; VAD/DTX и подавление пакетов запрещены. Таймаут 5000 мс относится к отсутствию пригодных пакетов, а не к амплитуде звука. Принятый и пригодный по сроку пакет тишины поддерживает поток; служебный ответ часов не заменяет аудио. Нулевая амплитуда сама по себе не является неисправностью."
        },
        {
          "type": "p",
          "text": "Приемка проверяет изменение TLS-метки, попытку UDP-инъекции во время разрешенного session.start, чужое TLS-соединение, старый mediaId, повтор пакета, задержку первого пакета, остановку TCP с последующим поступлением старых байтов, задержанный ответ часов, предельный дрейф и 10 секунд непрерывной кодированной тишины. Подлинность пакета и допустимый возраст проверяются раздельно. Приемка ДОЛЖНА включать соблюдение сроков TLS, акустического тракта и session.stop при заявленной нагрузке."
        },
        {
          "type": "p",
          "text": "Защищенный session.start разрешает воспроизведение, но не защищает отдельный открытый медиапоток. В режиме tls защищены и управление, и медиабайты. В plain такая гарантия отсутствует и не выводится из IP, порта, SSRC или mediaId. Для заранее проверенной локальной записи сетевого аудио во время исполнения нет, однако доверие к загрузке, неизменность хранения и достоверность физической обратной связи остаются самостоятельными условиями."
        },
        {
          "type": "heading",
          "id": "media-states",
          "text": "Воспроизведение, тангента и остановка"
        },
        {
          "type": "table",
          "headers": [
            "Событие медиатракта",
            "Требуемая реакция"
          ],
          "rows": [
            [
              "Подготовка принята",
              "Ресурсы, параметры и контекст зафиксированы; выход еще не считается воспроизводящим. Подготовка имеет конечный срок, а ready - конечный срок применимости."
            ],
            [
              "Первый пригодный пакет",
              "Учитывается только после проверки разрешения потока и условий выбранного профиля: привязки явно открытого канала либо криптографической проверки TLS, а также допустимого возраста. Подтверждает прием данных, но не готовность всего буфера и не акустическое воспроизведение."
            ],
            [
              "Буфер готов",
              "Воспроизведение начинается по согласованному правилу. Его наблюдение отделено от факта выдачи отсчетов программой; отсутствующая обратная связь явно ограничивает доказательство."
            ],
            [
              "Потеря или underrun",
              "Применяется заранее согласованная политика без перезапуска контента. Полнота результата ухудшается независимо от успешной последующей остановки."
            ],
            [
              "Штатный EOF потоковой записи",
              "Фиксируется конечное число действительных отсчетов; буфер доигрывается только до этой границы и в пределах срока дренирования. Пропуск в середине или потерянный хвост не превращаются в полное сообщение."
            ],
            [
              "Отпускание тангенты / отзыв источника",
              "Отпускание передает audio.finish: ограниченный хвост до sampleCount доигрывается. Принудительный отзыв выполняется session.stop/cancel и отбрасывает хвост. Завершение mediaId не освобождает владение УУ; новое нажатие создает новую подготовку и сеанс."
            ],
            [
              "session.stop/session.cancel, истечение срока или отзыв сеанса",
              "Прекращаются прием и будущая выдача звука, очищается тракт и подтверждается выключение. Режим дренирования штатного EOF не используется для принудительной отмены. Поздние команды и пакеты старого потока не затрагивают новый."
            ],
            [
              "Reboot или возврат сети",
              "Прежнее разрешение не восстанавливается автоматически. Новое включение требует актуального контекста и повторной подготовки; запоздалое аудио отбрасывается."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Тангента управляется владельцем сеанса: нажатие инициирует audio.prepare(source=microphone), подтвержденный prepared и session.start(mode=stream); отпускание - audio.finish. Физический источник может быть внешним устройством АРМ, но сетевым отправителем и носителем полномочий остается владелец. Передача слова другому участнику выполняется после конца и off прежнего сеанса. Потерянный prepared или accepted не разрешает начинать новый запуск; применяется чтение либо адресная отмена по общей модели."
        },
        {
          "type": "p",
          "text": "Подготовка истекает через 60000 мс от создания, запуск ограничен acceptBeforeMs и prepareTimeoutMs, поток - maxDurationMs и таймаутом отсутствия данных 5000 мс. Повторы не продлевают эти сроки. session.stop отзывает медиаразрешение и очищает буфер в stopBeginMs."
        },
        {
          "type": "p",
          "text": "Потеря управляющего соединения, истечение lease и потеря аудио являются разными событиями. Для локальной конечной записи и программы применяется зафиксированная stop/finish. Для stream всегда применяется stop при истечении lease; хвост неполученной записи не обещается к автономному воспроизведению. Гарантия окончания сообщения при потере АРМ требует локальной записи в mode=recording или program."
        },
        {
          "type": "p",
          "text": "Для микрофона потеря разрешения запрещает последующий звук; конкретный срок обнаружения потери источника задается профилем. Автоматическая подстановка резервной записи ДОПУСКАЕТСЯ только как заранее разрешенное действие с точной версией контента и отдельным результатом, а не как продолжение якобы полученной речи."
        },
        {
          "type": "p",
          "text": "При переходе \"сирена -> речь\" подтверждается завершение предыдущего воздействия, сохраняется происхождение запуска, подготавливается новый тракт и отзываются прежние отложенные команды. Частичная готовность группы обрабатывается по заранее выбранной политике с результатом каждого получателя; готовность части не выдается за готовность всей группы."
        },
        {
          "type": "p",
          "text": "Дренирование медиабуфера и draining владения - разные состояния разных автоматов, их нельзя отождествлять."
        },
        {
          "type": "heading",
          "id": "media-acceptance",
          "text": "Приемочные испытания речи"
        },
        {
          "type": "table",
          "headers": [
            "Приемка речевого профиля",
            "Проверяемый результат"
          ],
          "rows": [
            [
              "M01: контент",
              "Обрыв загрузки, подмена версии и неверный хеш не приводят к воспроизведению другого или неполного объекта. Производственное размещение и сетевое обновление проверяются отдельно."
            ],
            [
              "M02: подготовка",
              "Тракт готов, но аудио нет: истекает срок первого пакета, успешное воспроизведение не объявляется. Неподготовленный получатель не открывает выход по первым медиабайтам; UDP не принимается."
            ],
            [
              "M03: порядок и потери",
              "Дубликаты, перестановки, пропуск середины и хвоста, поздний пакет и переполнение буфера не создают повторного/запоздалого звучания или ложного полного результата."
            ],
            [
              "M04: точное окончание",
              "Неполный END-пакет несет validSamples. Задержка audio.finish на 200 мс, его опережение, потеря и повтор не меняют границу. Короткий поток меньше initialMs и пустой поток имеют определенные исходы; потерянный хвост не дает успеха."
            ],
            [
              "M05: отмена",
              "session.stop во время подготовки, воспроизведения и дренирования прекращает звук в измеренном бюджете; пакеты из сетевой и аппаратной очередей не возобновляют его."
            ],
            [
              "M06: тангента",
              "Отпускание, потеря источника, два претендента, поздняя выдача права и повтор старого поколения не разрешают новую передачу."
            ],
            [
              "M07: независимые каналы",
              "ДОЛЖНЫ проверяться отдельно: потеря управления при живом аудио, потеря аудио при живом управлении, полный обрыв и возврат связи. finish не объявляет доставленным неполученный хвост."
            ],
            [
              "M08: этапы и группа",
              "Поздний session.stop сирены не выключает новый речевой этап; отказ части приемников виден по каждому устройству и не заменяется общим успехом."
            ],
            [
              "M09: защита",
              "В tls отвергаются неверная метка, чужой principal, старый mediaId и UDP-инъекция. В plain проверяется только объявленная привязка и явно показывается отсутствие криптографической защиты. Переход tls в plain после ошибки запрещен; режимы проверяются отдельно."
            ],
            [
              "M10: ресурсный бюджет",
              "Сквозная задержка, буфер, время криптографии, память, число сокетов и задержка session.stop не превышают установленные пределы при одновременном управлении ОУ, опросе и нагрузке объявленного числа получателей."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Полное сообщение означает подтвержденное прохождение требуемого интервала без неучтенных пропусков на заявленном уровне наблюдения. Маскирование потери или выдача тишины не делает недоставленные слова доставленными. Цифровая полнота, работа усилителя и акустическое свидетельство сообщаются раздельно; слышимость и понимание населением из сетевых квитанций не выводятся. Критерии речевого оповещения охватывают управление, выбранный медиарежим, полноту отсчетов и физические выходы."
        },
        {
          "type": "heading",
          "id": "media-clock",
          "text": "Согласованное время и групповой запуск"
        },
        {
          "type": "note",
          "text": "Синхронный групповой запуск требует качества часов, допуска по времени, правил опоздания и результата частичной готовности. Профиль 0.20 не предоставляет синхронный запуск по UTC; такой запрос отклоняется с unsupported и не заменяется немедленным."
        },
        {
          "type": "p",
          "text": "Профиль согласованного времени фиксирует общую шкалу, источник синхронизации, максимальную погрешность и время удержания точности после потери источника. Название NTP или PTP само по себе не задает достигнутую точность. В межузловом поручении исходный абсолютный срок сохраняется: посредник НЕ ДОЛЖЕН продлевать его назначением нового полного TTL. При переводе на локальный монотонный таймер используется консервативный остаток до срока с учетом верхней границы ошибки часов; при недостоверной границе срок не считается проверенным. Монотонный maxDurationMs принятого исполнения считается отдельно от срока приема и коррекции настенных часов."
        },
        {
          "type": "heading",
          "id": "media-wire",
          "text": "Форматы речевого профиля rtp-alert/4"
        },
        {
          "type": "p",
          "text": "mediaId передается при привязке отдельного медиасоединения и однозначно определяет контекст всех последующих рамок. UDP-маршрут и ожидание «исчезновения старых пакетов из сети» не используются. После session.stop или reboot прежнее соединение закрывается, разрешение отзывается; его байты не принимаются в новом mediaId."
        },
        {
          "type": "p",
          "text": "Файл локального контента содержит только байты кодека, без WAV, RTP и иных контейнеров: PCMA - один байт A-law на отсчет, sampleCount=bytes; GSM - последовательность 33-байтовых кадров RFC 3551, bytes кратно 33 и 160*(bytes/33-1) < sampleCount <= 160*bytes/33. Последний GSM-кадр дополнен тишиной до 160 отсчетов; лишние отсчеты не воспроизводятся. Частота 8000 Гц, один канал. Хеш вычисляется над всеми передаваемыми байтами файла, включая дополненный последний кадр; длительность равна sampleCount/8000 секунд."
        },
        {
          "type": "p",
          "text": "Потоковый профиль rtp-alert/4 использует RTP версии 2 с обязательным расширением, описанным ниже, без CSRC и padding. PCMA: payload type 8, 8000 Гц, один канал, 20 мс, 160 байт отсчетов в пакете. GSM: payload type 3, 8000 Гц, один канал, 20, 40 или 80 мс, соответственно 33, 66 или 132 байта в формате RFC 3551. Изменение кодека, пакетизации, SSRC или маршрута требует нового mediaId; во время исполнения изменение запрещено. Профиль объявляет поддержку каждого варианта отдельно."
        },
        {
          "type": "table",
          "headers": [
            "Команда / элемент",
            "Формат и семантика rtp-alert/4"
          ],
          "rows": [
            [
              "audio.prepare / stream",
              "Изменяющая команда владельца с обычным cmdSeq. stream: source=recording|microphone, codec=PCMA|GSM, packetMs=20|40|80, ssrc=8 строчных hex, firstSeq=0..65535, firstTimestamp=0..4294967295; для recording обязателен totalSamples, для microphone запрещен. PCMA допускает только 20 мс. Внутри stream: resources из 1..4 resource id/bindingGeneration; один route; один buffer."
            ],
            [
              "route",
              "transport=tls|tcp; delivery=unicast; sourceAddress/destinationAddress — IPv4 без сокращений; sourcePort/destinationPort=1..65535; ttl=1..255. tls обязателен для защищенного управления, tcp — для явно открытого. Адреса/порты проверяются по разрешенной конфигурации; это маршрутизация, не удостоверение отправителя. Для rtp-alert/4 UDP и групповая доставка получают unsupported; отдельный профиль group-audio/1 задает свои параметры и формат."
            ],
            [
              "buffer",
              "initialMs=40|60|80|200|400; maxMs=80|120|160|400|1000, maxMs >= initialMs. Для microphone: initialMs=400, maxMs=1000. Время включает только буфер после проверки пакета. Отсутствие этих полей запрещено."
            ],
            [
              "Подготовленный ответ",
              "status=ok, prepared mediaId (32 hex), expiresAtMs, configRevision, capabilityRevision. Подготовка не включает выходы; занимает ограниченный буфер на 60000 мс, не продлеваемых повтором. mediaId связан с владельцем, эпохой, маршрутом и точными ресурсами. Один владелец имеет не более одного неиспользованного mediaId на УУ."
            ],
            [
              "session.start mode=stream",
              "Обычные поля session.start и resources; дочерний stream содержит только mediaId. УУ проверяет его срок, эпоху, ресурсы и ревизии. После accepted mediaId закрепляется до завершения сеанса и больше не применяется для нового session.start. Для stream onControlLoss ДОЛЖНО быть stop."
            ],
            [
              "audio.state",
              "Чтение: selection mediaId. Ответ audio с mediaId и sessionId после принятого session.start, state=prepared|buffering|playing|draining|finished, receivedPackets, lostPackets, latePackets, duplicatePackets, emittedSamples, bufferedMs и lastPacketAtMs. Все счетчики uint64; счетчики отсутствовавших событий равны 0; дополнительно clock по правилам частоты ниже. Чтение не продлевает lease."
            ],
            [
              "audio.finish",
              "Изменяющая команда владельца: stream mediaId, lastPacketIndex и sampleCount. Индексы расширенные, начиная с 0; sampleCount - полное число отсчетов источника. Ответ status=ok, eof mediaId/lastPacketIndex/sampleCount; несовпадающий повтор дает conflict. EOF согласуется с END и запрещает новые индексы выше lastPacketIndex; после finished совпадающая граница подтверждается без изменения результата. В состоянии prepared до session.start команда отклоняется с unsafeState."
            ],
            [
              "Отмена",
              "session.stop/cancel останавливает связанное исполнение и отзывает mediaId; поступающие пакеты больше не декодируются и не выводятся. Отдельных audio.start и audio.stop нет. Непринятая подготовка отменяется audio.discard с prepared mediaId; ответ status=ok, discarded mediaId. Активный mediaId через discard не отзывается: unsafeState."
            ]
          ]
        },
        {
          "type": "p",
          "text": "packetIndex передается явно в расширении rtp-alert/4 и проверяется вместе с sequence/timestamp по формулам ниже. Индексы не превышают 2^31-1; окно перестановки 64 пакета относительно старшего принятого индекса. Дубликаты и просроченные пакеты не воспроизводятся. Кодековая длина фиксирована; validSamples последнего пакета отделяет полезный хвост от дополнения тишиной до декодирования на выход."
        },
        {
          "type": "p",
          "text": "Источник устанавливает отдельное медиасоединение к подготовленному destinationAddress/destinationPort с согласованным локальным sourceAddress/sourcePort. Первое прикладное сообщение имеет рамку uint32 BE + XML: request v=1/profile=open-alert-draft/0.20/id/action=media.hello/bootId/controlEpoch и stream mediaId. protocol.hello на этом канале отсутствует. В tls проверяются principalId и полная привязка; в tcp — открытый principal и контекст, без криптографической аутентификации. УУ отвечает response v/profile/to/bootId/status=ok и ready mediaId. Ответ передается целиком до бинарных рамок. Далее применяются обрамление и проверка возраста ниже: RTP до 192 байт для PCMA, 65/98/164 байта для GSM с packetMs=20/40/80 соответственно. Отказ до привязки закрывает только входящий канал; ошибка связанного канала обрабатывается по правилам изоляции. Медиа не продлевает управляющий lease."
        },
        {
          "type": "p",
          "text": "После accepted поток накапливается до initialMs, кроме полного короткого потока по правилу ниже. Истечение prepareTimeoutMs до running дает prepareTimeout. Буфер и обработка потерь подчиняются правилам конечной границы и частоты ниже; для rtp-alert/4 FEC, повторная передача RTP и автоматическая смена кодека запрещены. Принудительный session.stop удаляет все еще не выданные отсчеты, включая корректный END."
        },
        {
          "type": "p",
          "text": "Граница конца передается в END-пакете и дублируется audio.finish. Ее точная проверка, порядок при запаздывании управляющего сообщения, пустой и короткий поток определены ниже. Ни одно ожидание EOF не продлевает prepareTimeoutMs/maxDurationMs. Повторное нажатие тангенты создает новый mediaId и новый сеанс; старый mediaId не возобновляет звук."
        },
        {
          "type": "p",
          "text": "Сетевая загрузка локального контента использует content.begin/commit/status/cancel с административным правом и обычным cmdSeq у изменений; данные передаются bulk/1. begin содержит content id/revision/codec/bytes/sampleCount/hashAlgorithm/hash и отдельный transfer profile/blockBytes/windowBlocks/windowRoundTripMaxMs. codec=PCMA|GSM, bytes:1..33554432. Ответ upload id/receivedBytes/remainingBudgetMs; новая транзакция имеет receivedBytes=0, повтор возвращает сохраненный префикс и прежний ID. commit содержит upload id и ДОПУСКАЕТСЯ после полного приема; проверяет длину, формат, sampleCount и Стрибог-256, затем атомарно публикует неизменный contentId/revision. Синхронный ответ content id/revision/state=committed либо accepted с operation.id=upload.id и последующим content.status. status является чтением selection uploadId и сообщает состояния и поля bulk/1 в разделе 14. cancel с upload id допустим до атомарного commit, включая verifying, ответ upload id/state=cancelled. Действующая ревизия не стирается при незавершенной загрузке, совпадающие id/revision с другими байтами дают conflict. Заводское размещение создает тот же манифест и результат проверки байтов."
        },
        {
          "type": "heading",
          "id": "media-boundary-traces",
          "text": "Эталонные трассы окончания и синхронизации"
        },
        {
          "type": "table",
          "headers": [
            "Трасса",
            "Входные условия",
            "Обязательный исход"
          ],
          "rows": [
            [
              "late-eof",
              "PCMA: полный пакет index=0, END index=1 с validSamples=40; audio.finish приходит через 200 мс после END.",
              "sourceSamples=200, concealedSamples=0; 120 отсчетов дополнения не выдаются как звук источника; поздний finish подтверждает границу 200."
            ],
            [
              "full-boundary",
              "Микрофон отправил полный пакет index=0 без END, затем остановился.",
              "END index=1, validSamples=0, нагрузка 0; sourceSamples=160, второго звукового пакета нет."
            ],
            [
              "short-stream",
              "END index=0, validSamples=40 приходит до накопления initialMs=60.",
              "Начать выдачу после получения полного короткого потока, потребить 40 отсчетов источника; не ждать 60 мс."
            ],
            [
              "missing-final",
              "Микрофон уже running: полные пакеты index=0..2; END index=3 с validSamples=40 утрачен; получен надежный EOF sampleCount=520.",
              "40 отсчетов замены; failed/mediaIncomplete, успех запрещен."
            ],
            [
              "clock-drift",
              "Потерь нет, джиттер доставки до 20 мс; дрейф +200 ppm в течение 600 с, затем отдельные трассы -200 ppm и изменения 1 ppm/с.",
              "Нет удаления/повтора пакетов, underrun и переполнения; sourceSamples отделен от счетчика ЦАП; среднее заполнение за 1 с после 10 с находится в initialMs ± один пакет."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "media-end-and-clock",
          "text": "Конечная граница, короткий поток и частота воспроизведения"
        },
        {
          "type": "table",
          "headers": [
            "Заголовок расширения RTP rtp-alert/4",
            "Точное представление"
          ],
          "rows": [
            [
              "RTP",
              "V=2, P=0, X=1, CC=0. Обычный заголовок 12 байт. За ним 16-битный идентификатор 0x534f и 16-битная длина 4 (четыре 32-битных слова без четырехбайтового заголовка расширения). Все многобайтовые целые big-endian."
            ],
            [
              "16 байт данных расширения",
              "packetIndex:uint32, validSamples:uint16, flags:uint16, capturedAtMs:uint64. Единственный флаг END=0x0001; остальные биты нулевые. Полный заголовок RTP с расширением - 32 байта."
            ],
            [
              "Обычный пакет",
              "END=0, validSamples=N=packetMs*8. Полезная нагрузка: PCMA 160 байт; GSM 33, 66 или 132 байта. Индекс начинается с 0; sequence=(firstSeq+packetIndex) mod 65536, timestamp=(firstTimestamp+N*packetIndex) mod 2^32. marker=1 только при packetIndex=0."
            ],
            [
              "Последний пакет",
              "END=1, 1<=validSamples<=N. Нагрузка той же кодековой длины с дополнением тишиной, но декодер передает только validSamples. sampleCount=packetIndex*N+validSamples. Этот пакет задает границу до выдачи собственного хвоста независимо от доставки audio.finish."
            ],
            [
              "Маркер границы",
              "END=1, validSamples=0 и длина нагрузки 0 допустимы у микрофона после последнего уже отправленного полного пакета: packetIndex равен следующему индексу, sampleCount=packetIndex*N. Маркер не является звуковым пакетом и не увеличивает lostPackets при его утрате. При packetIndex=0 поток пуст: выходы не включаются, итог cancelled/operatorStop с подтвержденным off."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Для source=recording audio.prepare содержит обязательный totalSamples:uint64>0; для microphone это поле запрещено. У записи заранее известны lastPacketIndex=floor((totalSamples-1)/N) и допустимые отсчеты хвоста; END и audio.finish ДОЛЖНЫ совпасть с этой границей. Для микрофона конец определяется END либо заранее поступившим audio.finish. Если полный последний пакет уже отправлен без END, источник передает маркер границы с нулем отсчетов; повторная передача прежнего индекса с измененными флагами запрещена. lastPacketIndex в audio.finish обозначает индекс END, включая такой маркер. При sampleCount=0 индекс равен 0; иначе допустимы обычный хвост с (lastPacketIndex*N)<sampleCount<=(lastPacketIndex+1)*N либо маркер sampleCount=lastPacketIndex*N. У записи обязателен END в последнем звуковом пакете, отдельный нулевой маркер не используется. Два разных конца дают failed/mediaIncomplete до завершения; после finished противоречивое сообщение дает conflict и не переписывает историю. audio.finish повторяет lastPacketIndex/sampleCount, подтверждается eof; доставка после воспроизведения корректного END допустима и возвращает ту же границу в течение resultRetentionMs, даже если сеанс уже finished. Отсутствие audio.finish при полученном корректном END не является потерей звука."
        },
        {
          "type": "p",
          "text": "УУ никогда не выдает дополнение последнего кодекового кадра как отсчеты источника. Короткий непустой поток меньше initialMs начинает выдачу, как только получены конечная граница и все его пакеты; ожидание полного стартового буфера в этом случае запрещено. В остальных случаях старт при накоплении не менее initialMs; округление вверх до целого кодекового пакета ДОПУСКАЕТСЯ только в пределах maxMs. Отсутствие полного короткого потока к prepareDeadlineMs дает prepareTimeout. Пустой поток не требует running и не считается ошибкой подготовки."
        },
        {
          "type": "p",
          "text": "Потерянная позиция учитывается как lostPackets и замена тишиной только если ее принадлежность источнику доказана totalSamples, EOF либо наличием более позднего пакета. За еще неизвестным концом УУ может выдавать аппаратную тишину, но не начисляет фиктивные отсчеты источника и потери. При появлении более позднего пакета пропущенные позиции до него классифицируются один раз; поздние байты не воспроизводятся задним числом. Отсутствие пригодных пакетов 5000 мс без полного конца дает mediaIncomplete для записи и sourceUnavailable для микрофона. При потерянном END надежный EOF задает диапазон; отсутствие данных в нем дает failed/mediaIncomplete для обоих источников, если выключение подтверждено. При неизвестном выключении итог unknown/stopUnconfirmed. Все ожидания ограничены prepareTimeoutMs/maxDurationMs и не задерживают session.stop."
        },
        {
          "type": "p",
          "text": "RTP timestamp описывает шкалу источника; часы ЦАП не считаются тождественными ей. Приемник ДОЛЖЕН поддерживать непрерывное воспроизведение при постоянном относительном расхождении частот до ±200 ppm, а также плавном изменении до 1 ppm/с внутри этого диапазона. Используется подстройка частоты ЦАП или непрерывный ресемплинг; коррекция ограничена ±500 ppm. Сброс timestamp, периодическое удаление целого пакета и повтор уже потребленного пакета для регулировки буфера запрещены. Алгоритм оценки не нормируется, но результат обязан удовлетворять указанным границам и испытательным трассам."
        },
        {
          "type": "p",
          "text": "В отсутствие потерь, при джиттере доставки до 20 мс и указанном дрейфе УУ удерживает заполнение без underrun и без превышения maxMs; после первых 10000 мс среднее заполнение за скользящее окно 1000 мс находится в пределах initialMs±один пакет, с учетом округления стартового порога. Изменение initialMs/maxMs во время сеанса запрещено. При переполнении, невозможности удержать скорость или оценке расхождения более 500 ppm непрерывно 2000 мс поток останавливается с failed/mediaClockFault; старые данные не вытесняются молча. Условия за пределами заявленной области отражаются отказом, а не бесконечным ростом задержки."
        },
        {
          "type": "p",
          "text": "audio.state сохраняет emittedSamples как число отсчетов, выданных ЦАП, включая ресемплирование и аппаратную тишину до off. Дополнительный дочерний clock содержит sourceSamples, concealedSamples, rateCorrectionPpm и state=locking|locked|outOfRange. sourceSamples - число однократно потребленных позиций источника внутри известной границы, включая позиции замены; concealedSamples - сколько из них заменены из-за утраты, без добавленных кодековых отсчетов и тишины за неизвестным концом. rateCorrectionPpm - int от -500 до 500. При успехе конечного потока sourceSamples=sampleCount, concealedSamples=0; emittedSamples может отличаться из-за коррекции часов. При ресемплинге длина считается в шкале источника, полнота не требует равенства счетчикам ЦАП."
        },
        {
          "type": "heading",
          "id": "media-binding-isolation",
          "text": "Изоляция отказов подключения медиаканала"
        },
        {
          "type": "p",
          "text": "До успешного media.hello входящее TLS-соединение не связано с подготовкой и не имеет полномочий на сеанс. УУ сначала проверяет principalId, bootId, controlEpoch, выбранный профиль, mediaId, актуальность подготовки и отсутствие уже привязанного соединения. Любой отказ закрывает только входящее соединение; он НЕ ДОЛЖЕН отзывать mediaId, очищать буфер, останавливать исполнение или изменять его результат. Знание mediaId, включая верный идентификатор чужого сеанса, не является полномочием. Повторное соединение того же principalId при занятой привязке также не вытесняет действующий канал."
        },
        {
          "type": "p",
          "text": "Успешная привязка выполняется атомарно и закрепляет конкретный экземпляр TLS-соединения за mediaId. Ошибка формата, целостности или потеря именно этого соединения после привязки прекращает его подготовку либо инициирует штатную остановку связанного исполнения. Отложенный обработчик проверяет идентичность экземпляра соединения и контекст; закрытие старого либо непривязанного соединения не затрагивает другой поток. После утраты привязанного соединения для нового звука ТРЕБУЕТСЯ новая подготовка и новый mediaId. Полномочия существующего сеанса не наследуются неуспешной попыткой подключения."
        },
        {
          "type": "p",
          "text": "Штатный session.stop/cancel отзывает поток и прекращает воспроизведение без требования передать оставшуюся часть файла, END или audio.finish. Отсчеты после принятой границы остановки и сброшенный по ней буфер не учитываются как потерянный звук. Для незавершенного файла completion=interrupted; при подтвержденном начале на всех ресурсах и off в срок ДОПУСКАЕТСЯ succeeded/operatorStop. Уже зафиксированные до остановки mediaIncomplete, sourceUnavailable, mediaClockFault и аппаратные отказы сохраняются и запрещают успех. Отмена не является способом скрыть потерю звука до своей границы."
        }
      ]
    },
    {
      "id": "maintenance",
      "title": "14. Конфигурация, контент и обновление",
      "blocks": [
        {
          "type": "p",
          "text": "Изменения проходят подготовку, проверку и явное применение. config.stage не меняет действующую конфигурацию; config.commit атомарно фиксирует и применяет проверенные локальные правила получателя; configRevision и appliedRevision становятся равны. Команды физической перенастройки ОУ и перезапуск в этот пакет не входят. Несколько получателей применяют пакет независимо и имеют отдельные результаты. Значение onControlLoss хранится на конкретном приборе и не переопределяется произвольным session.start. После применения выполняется чтение фактической конфигурации."
        },
        {
          "type": "p",
          "text": "Сеть меняется с подтверждением через новый путь и возвратом к прежним параметрам при истечении срока. Потеря ответа commit требует чтения результата по идентичности операции, а не слепого повторного применения. Секреты не возвращаются обычным config.get."
        },
        {
          "type": "heading",
          "id": "maintenance-firmware",
          "text": "Обновление и восстановление"
        },
        {
          "type": "p",
          "text": "Прошивка принимается малыми блоками с проверкой совместимости, целостности и подлинности образа. Загрузка, проверка, активация и результат загрузчика различаются. В активном оповещении недопустимы перезапуск, смена профиля ОУ и изменение задействованного контента. Сервисная передача НЕ ДОЛЖНА занимать неограниченную очередь перед session.stop."
        },
        {
          "type": "p",
          "text": "Конфигурация и контент фиксируются атомарно: действующая копия сохраняется до проверки и commit-маркера новой; после потери питания выбирается последняя целая зафиксированная ревизия. Допустимый откат выполняется как новая ревизия уполномоченного автора. Загрузчик сохраняет исполнимый образ либо независимый режим приема образа при обрыве любой операции записи. Если такая гарантия не обеспечивается аппаратурой, удаленная активация прошивки ДОЛЖНА быть запрещена."
        },
        {
          "type": "p",
          "text": "Изменяющие сервисные команды допускаются участнику с административным правом, при владении purpose=maintenance и подтвержденном off затрагиваемых ресурсов. Чтения config.get, firmware.status и content.status владения не требуют. Изменения используют обычный cmdSeq; одна активная транзакция изменения на УУ. Транзакция переживает смену эпохи по правилам продолжения, но старая эпоха больше не меняет ее. Сервис уступает реальной тревоге по разделу 3; секретные ключи в конфигурацию не входят."
        },
        {
          "type": "table",
          "headers": [
            "Команды",
            "Предметный формат и переход"
          ],
          "rows": [
            [
              "config.get",
              "Чтение selection с configId либо transactionId или snapshotId/cursor. Ответ configuration: snapshotId, configRevision, appliedRevision, more/cursor; записи entry id/facet/part/parts/type/text для разрешенных параметров либо transaction id/state/receivedBytes/totalBytes/hash/remainingBudgetMs. Путь и значение до 128 байт передаются отдельными частями по 64 байта; обе facet и все части обязательны по разделу 5."
            ],
            [
              "config.stage",
              "transaction id/baseRevision/totalBytes/hashAlgorithm/hash и transfer profile/blockBytes/windowBlocks/windowRoundTripMaxMs. id:32 hex; totalBytes:1..1048576. Создает прием bulk/1 без изменения текущей конфигурации; байтов block в XML нет. Ответ transaction id/state=receiving/receivedBytes/remainingBudgetMs. Повтор возвращает текущий префикс/состояние. Полный файл проходит validating и получает staged только после проверки длины, хеша, JSON-пакета и прав всех изменений; config.get читает прогресс и исход."
            ],
            [
              "config.commit / config.cancel",
              "transaction id. commit требует staged, совпадения baseRevision с текущей и отсутствия исполнения; атомарно фиксирует и применяет локальные правила получателя, без записи параметров ОУ; точная граница и исходы определены ниже. Ответ committed id/configRevision/appliedRevision. cancel удаляет только незафиксированную транзакцию, ответ cancelled id. Точный повтор использует сохраненное решение; cancel после commit дает conflict."
            ],
            [
              "network.stage",
              "network id/address/prefix/gateway/confirmMs. id:32 hex, IPv4, prefix:0..32, confirmMs:10000..60000. Прежняя конфигурация хранится до подтверждения; ответ staged id/expiresAtMs. Новый адрес активируется как пробный, старый путь сохраняется на период подтверждения, если аппаратно допустимо."
            ],
            [
              "network.confirm / network.cancel",
              "network id. confirm принимается через новый адрес до expiresAtMs, с действующего управляющего соединения того же principalId и того же явно выбранного режима tls либо plain, что network.stage. Перед confirm новый канал проходит protocol.hello и control.resume действующей эпохи; если она истекла, тот же участник получает новое control.acquire purpose=maintenance. Чужой principalId, смена режима, прежняя привязка и старый адрес не подтверждают переход. Для tls principalId аутентифицирован, для plain остается логическим идентификатором. confirm фиксирует новые параметры; cancel или истечение срока восстанавливает прежние. Ответ network id/state=committed|cancelled. Повтор, новое соединение и смена эпохи не продлевают expiresAtMs. При невозможности надежного возврата network.stage отклоняется до смены адреса."
            ],
            [
              "time.set",
              "clock utcMs/errorMs; uint64, errorMs:0..500. Изменяет UTC и оценку его погрешности; uptimeMs, lease, временная шкала и уже вычисленные сроки исполнения неизменны. Ответ clock utcMs/errorMs."
            ],
            [
              "firmware.begin",
              "image id/deviceKind/hardwareRevision/version/bytes/hashAlgorithm/hash/signature/keyId и transfer profile/blockBytes/windowBlocks/windowRoundTripMaxMs. id:32 hex; version uint64; bytes:1..33554432; подпись - 128 строчных hex, ГОСТ Р 34.10-2012, 256-битный вариант и GC256A, над Стрибог-256 манифеста. keyId выбирает предварительно внесенный доверенный ключ издателя. Ответ upload id=image.id/state=receiving/receivedBytes=0/remainingBudgetMs; байты поступают bulk/1."
            ],
            [
              "firmware.verify",
              "upload id. Проверяет размер, хеш байтов, подпись манифеста, совместимость device.kind/hardwareRevision и разрешенную version. Ответ upload id/state=verified либо status=accepted с operation id=upload.id. firmware.status сообщает verifying/verified/failed; failed содержит reason. Проверка ограничена 60000 мс и выполняется порциями до 100 мс. Правило владения во время проверки полного файла исключает прерывание обычным idle-сроком 10000 мс, но сохраняет lease и отзыв в пользу real. Отзыв владения во время verifying возвращает complete без продления общих сроков."
            ],
            [
              "firmware.commit / firmware.status",
              "commit: upload id, только verified и off; ответ accepted с operation id. Активируется только образ с сохраненным путем восстановления. status: selection operationId; ответ firmware operationId/state=receiving|complete|verifying|verified|activating|booted|failed|cancelled, receivedBytes/version/reason. booted подтверждается новым bootId и фактической version после загрузки, квитанция загрузчика не является booted."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Манифест образа - JCS JSON с полями deviceKind, hardwareRevision, version, bytes, hashAlgorithm=\"streebog256\", hash. Все целые числа представлены десятичными строками. Подпись кодируется как 32-байтовое r, затем 32-байтовое s, оба в little-endian, в строчных hex. Производитель поддерживает монотонный минимальный разрешенный номер версии; откат ниже него дает rollbackDenied. Для передачи применяется расчетный бюджет и idleTimeoutMs по таблице ниже; повтор не сдвигает сроки. Состояния операции сохраняются до явной отмены или истечения, а результат commit - по общему resultRetentionMs."
        },
        {
          "type": "heading",
          "id": "maintenance-transfer-budget",
          "text": "Идентичность сервисной транзакции"
        },
        {
          "type": "p",
          "text": "Одна незавершенная транзакция изменения на УУ; она связана с principalId и неизменным манифестом, а не с эпохой. firmware upload.id= image.id=operation.id во всех стадиях от begin до booted; content использует один upload.id для приема, проверки и commit; config использует transaction.id. Повтор begin/stage своего участника проверяет весь манифест и параметры bulk/1, возвращает прежний ID и текущий receivedBytes без обнуления срока. Изменение любого поля дает conflict. Параметры канала, долговременные ACK, сроки и продолжение определены ниже в bulk/1. Терминальный failed/cancelled освобождает staging и сохраняет итог не менее resultRetentionMs; повтор ID с другим манифестом не превращается в новую транзакцию."
        },
        {
          "type": "p",
          "text": "firmware.cancel: upload id, обычный cmdSeq владельца maintenance и право обновления. В receiving/complete/verifying/verified отменяет прием/проверку, возвращает upload id/state=cancelled и освобождает незакоммиченный образ; в activating/booted/failed - conflict; cancelled возвращает сохраненный итог. Повтор возвращает сохраненный итог. firmware.verify принимает complete, синхронный ответ verified либо accepted с operation.id; firmware.status всегда содержит operationId/state/receivedBytes/version/remainingBudgetMs; в verifying дополнительно progressBytes, в failed - reason (код ошибки), в иных состояниях reason отсутствует. remainingBudgetMs=0 для терминального результата, иначе остаток действующего срока. commit допустим только в verified, задает activating и сохраняет ID в журнале загрузчика; booted относится к фактической новой version и bootId."
        },
        {
          "type": "p",
          "text": "config.commit атомарен только для конфигурации самого получателя: хранение полного JSON-пакета и переключение локального набора правил. В 0.20 пакет не содержит команд записи параметров ОУ, секретных ключей или изменения собственной аппаратной идентичности. Открытые ключи источников защищенной групповой речи задаются только в mediaGroups по специальным правилам этого профиля. Все ссылки, доступность локальных объектов и возможность применить правила без записи параметров в ОУ проверяются до commit. Успешный ответ committed содержит configRevision=appliedRevision новой редакции. Отказ до атомарной фиксации сохраняет обе старые редакции. Потеря ответа после фиксации выясняется через config.get/transaction.id. Групповой импорт на несколько узлов не атомарен: каждый узел имеет собственный итог. Физическая перенастройка ОУ этим пакетом не выполняется и не объявляется примененной."
        },
        {
          "type": "heading",
          "id": "maintenance-bulk",
          "text": "Оконная передача файлов bulk/1"
        },
        {
          "type": "p",
          "text": "При объявлении сетевого размещения контента, транзакционной конфигурации или обновления прошивки обе стороны ДОЛЖНЫ поддерживать bulk/1. Управляющие команды создают, проверяют и фиксируют объект; его байты передаются отдельным TCP-соединением того же явно выбранного режима tls либо plain. В open-alert-draft/0.20 команды content.write и firmware.write отсутствуют, config.stage не содержит block. Использование 64-байтового обмена прежних редакций внутри 0.20 запрещено; неподдержанный action получает unsupported по общему правилу cmdSeq. Устройства без указанных файловых функций не обязаны реализовывать bulk/1."
        },
        {
          "type": "p",
          "text": "transfer.capabilities выполняется на управляющем соединении без владения, с правом чтения возможностей. Запрос не содержит дочерних элементов. Ответ status=ok содержит единственный transfer: profile=\"bulk/1\", port, maxBlockBytes, maxWindowBlocks, maxInFlightBytes. port:1..65535 задает отдельный от управления и медиа TCP-порт того же аутентифицированного УУ; иной адрес и переадресация запрещены. Допустимые maxBlockBytes:4096,8192,16384,32768,65536; maxWindowBlocks:4..64; maxInFlightBytes:16384..4194304 и не меньше 4*maxBlockBytes. Изменение этих возможностей меняет capabilityRevision. Минимальная совместимая пара: blockBytes=4096, windowBlocks=4."
        },
        {
          "type": "p",
          "text": "firmware.begin, content.begin и config.stage содержат рядом с предметным манифестом единственный transfer с profile=\"bulk/1\", blockBytes, windowBlocks, windowRoundTripMaxMs. blockBytes выбирается из указанного ряда, не больше maxBlockBytes; windowBlocks:4..maxWindowBlocks, произведение не больше maxInFlightBytes. windowRoundTripMaxMs:1..10000 — объявленный бюджет передачи и долговременного подтверждения полного окна, включая сеть, TLS, накопление ACK, Flash, планирование и необходимое восстановление владения. Невозможность принять точные параметры дает unsupported до резервирования; скрытого уменьшения блока или окна нет. Параметры входят в неизменный манифест транзакции; повтор begin/stage с другими параметрами дает conflict. Подписанный манифест образа сохраняет прежний состав и не включает параметры транспорта."
        },
        {
          "type": "p",
          "text": "УУ резервирует staging-хранилище до подтверждения начала. Окно определяет число байтов в полете, а не обязательный массив такого размера в SRAM: приемник может порционно записывать проверенные данные выбранного режима (в tls — только после аутентификации MGM) в неактивную область и использовать ограниченный буфер записи. Непроверенная метка TLS не дает права публиковать либо подтверждать данные. Паспорт изделия задает память TLS/парсера/записи, размер очередей, геометрию Flash и максимальную непрерываемую операцию. Ресурсы управляющего канала, взаимодействия с ОУ и отмены резервируются независимо; недостаточность ресурсов отклоняет начало до изменения действующего объекта."
        },
        {
          "type": "heading",
          "id": "maintenance-bulk-binding",
          "text": "Привязка и ограждение файлового соединения"
        },
        {
          "type": "p",
          "text": "Файловый TCP-порт принимает тот же режим соединения и principalId, что управление: TLS 1.3 с проверкой ключей для tls, открытый TCP для plain; отдельная политика доверия не подменяет управляющую. Для tls сохраняются record_size_limit=512 и запреты 0-RTT/fallback. Бинарный блок может пересекать любое число TLS-записей и TCP-сегментов; размер блока не увеличивает лимит записи. Первое прикладное сообщение — обычная рамка uint32 BE + XML до 1024 байт: request v/profile/id/action=\"transfer.hello\"/bootId/controlEpoch, один transfer id/kind. kind=config|content|firmware; id равен transaction.id для config и upload.id для остальных. profile равен выбранному open-alert-draft/0.20. protocol.hello и cmdSeq на этом соединении не передаются."
        },
        {
          "type": "p",
          "text": "Привязка требует receiving, действующего управляющего соединения того же principalId, текущих bootId/controlEpoch, владения maintenance, соответствующего административного права и отсутствия иной файловой привязки. УУ атомарно связывает транзакцию с конкретными экземплярами управляющего и файлового соединений. До успеха ошибка закрывает только входящий канал, не меняет транзакцию, ее срок или действующее соединение. Ответ — обычная response v/profile/to/bootId/status=ok, один ready id/blockBytes/windowBlocks/receivedBytes/remainingBudgetMs. Величины блока и окна точно совпадают с begin; receivedBytes — устойчивый префикс на момент привязки. Ответ передается полностью до первого бинарного кадра. После него XML на файловом соединении запрещен."
        },
        {
          "type": "p",
          "text": "Потеря или замена привязанного управляющего соединения, отзыв/освобождение/истечение эпохи либо прав, cancel, reboot и срок транзакции закрывают ее файловую привязку. Перед каждой долговременной фиксацией префикса повторно проверяются экземпляры соединений, principalId, bootId и controlEpoch. Запись, еще не зафиксированная при отзыве, не становится подтвержденной позже. Текущий сервисный шаг ограничен 100 мс; отзыв и ответ real сохраняют предел 500 мс. Медленный отправитель, заполненный TCP-буфер и ожидание ACK не удерживают эту границу. Поздний callback старого соединения не закрывает и не изменяет новую привязку. После разрыва файлового соединения тот же участник выполняет новый transfer.hello; если управляющее соединение и maintenance-эпоха еще действуют, они сохраняются. При утрате владения сначала получается новое maintenance-владение. Старый файловый канал не возобновляется."
        },
        {
          "type": "heading",
          "id": "maintenance-bulk-wire",
          "text": "Двоичные кадры и подтверждения"
        },
        {
          "type": "table",
          "headers": [
            "Смещение",
            "Поле",
            "Представление"
          ],
          "rows": [
            [
              "0..3",
              "magic",
              "4 байта ASCII OAB1: 4f 41 42 31."
            ],
            [
              "4",
              "type",
              "uint8: 1 DATA, 2 ACK, 3 ERROR."
            ],
            [
              "5",
              "flags",
              "uint8=0; неизвестные биты запрещены."
            ],
            [
              "6..7",
              "reserved",
              "uint16=0."
            ],
            [
              "8..11",
              "payloadBytes",
              "uint32 big-endian: число байтов после 20-байтового заголовка."
            ],
            [
              "12..19",
              "offset",
              "uint64 big-endian: смещение DATA или устойчивый receivedBytes для ACK/ERROR."
            ]
          ]
        },
        {
          "type": "table",
          "headers": [
            "Кадр",
            "Поля",
            "Эталонные байты hex"
          ],
          "rows": [
            [
              "single-tail",
              "DATA; offset=0",
              "4f41423101000000000000030000000000000000616263"
            ],
            [
              "tail-after-block",
              "DATA; offset=4096",
              "4f41423101000000000000030000000000001000010203"
            ],
            [
              "prefix-ack",
              "ACK; offset=4096",
              "4f41423102000000000000000000000000001000"
            ],
            [
              "stale-epoch",
              "ERROR; offset=4096",
              "4f414231030000000000000200000000000010000004"
            ]
          ]
        },
        {
          "type": "p",
          "text": "Каждый кадр состоит ровно из заголовка и payloadBytes байт. Проверка type, flags, reserved, размера, смещения, переполнения и окна выполняется до выделения памяти и записи полезной нагрузки. Неполный заголовок/блок при EOF не считается DATA. DATA передается только АРМ → УУ; ACK и ERROR только УУ → АРМ. Неверное направление, magic, тип, зарезервированное поле либо длина закрывают файловое соединение. Получатель может отправить ERROR, если заголовок достоверно разобран и отправка не задерживает управление; после ошибки продолжение на этом соединении запрещено. Невыдача ERROR не разрешает угадывать исход — клиент читает статус на управляющем канале."
        },
        {
          "type": "p",
          "text": "DATA имеет payloadBytes=blockBytes, кроме последнего блока, содержащего остаток 1..blockBytes; offset кратен blockBytes, offset+payloadBytes<=totalBytes. После hello первый offset равен полученному receivedBytes. Следующий DATA начинается точно после предыдущего целиком принятого DATA; пропуски, перекрытия и повтор на одном соединении запрещены. TCP уже обеспечивает порядок и повторную доставку; приложение не пересылает блок повторно поверх живого потока. После обрыва прием возобновляется только от устойчивого префикса нового hello, включая случай потерянного ACK. При receivedBytes=totalBytes hello НЕ ДОПУСКАЕТСЯ: клиент читает состояние и выполняет проверку/commit."
        },
        {
          "type": "p",
          "text": "ACK имеет payloadBytes=0, offset=receivedBytes; он подтверждает весь непрерывный префикс, переживающий потерю питания. receivedBytes кратен blockBytes либо равен totalBytes. ACK не подтверждает хеш всего файла, подпись, staged, verified или commit. УУ поддерживает отдельно следующий принимаемый offset и устойчивый receivedBytes; после разрыва отбрасывает незафиксированный хвост. На одном соединении ACK не уменьшается и не превышает конец отправленных данных. Повтор ACK допустим, но не является продвижением. ACK старого экземпляра соединения игнорируется клиентом."
        },
        {
          "type": "p",
          "text": "Пусть A — последний полученный клиентом ACK, а перед первым DATA A=receivedBytes из hello. Конец каждого отправляемого DATA ДОЛЖЕН быть не больше min(totalBytes,A+blockBytes*windowBlocks). Все разрешенные окном блоки могут отправляться без ожидания отдельных квитанций. УУ не уменьшает согласованное окно во время привязки; при недостатке ресурсов использует ограниченное TCP-противодавление, сохраняя сроки управления, либо закрывает канал с сохранением устойчивого префикса. УУ фиксирует и подтверждает поступивший непрерывный префикс, не ожидая заполнения окна: при полученном DATA прогресс ACK ДОЛЖЕН укладываться в windowRoundTripMaxMs от первого неподтвержденного DATA с учетом оговоренного сетевого бюджета. После долговременной фиксации ACK ставится на отправку не позднее 20 мс. Совокупный срок все равно ограничен windowRoundTripMaxMs; таймеры ACK не складываются сверх него."
        },
        {
          "type": "p",
          "text": "Отправитель сохраняет время начала отправки каждого неподтвержденного DATA. Ни один такой блок не остается без долговременного подтверждения дольше windowRoundTripMaxMs; частичный ACK не обновляет время остальных блоков. Дополнительно для расчетных групп до S*W байт полный оборот от начала отправки группы до ACK ее конца укладывается в заявленный бюджет окна, включая сериализацию. При превышении срока самого старого блока клиент закрывает только файловое соединение, читает status/get, восстанавливает владение при необходимости и привязывается заново. Эти действия не увеличивают общий срок. Проверка связи выполняется по управляющему каналу; DATA, ACK и transfer.hello не продлевают lease. Сохраненный новый префикс ДОЛЖЕН освежать maintenance idleDeadlineMs до uptimeMs+10000, ограниченного сроком транзакции; чтения, повторы и пустое соединение не освежают его. Это не препятствует немедленному отзыву maintenance в пользу real."
        },
        {
          "type": "p",
          "text": "ERROR имеет payloadBytes=2, payload — uint16 big-endian с кодом: 1 invalidArgument, 2 conflict, 3 forbidden, 4 staleEpoch, 5 resultExpired, 6 storageFault, 7 busy, 8 unsafeState. offset сообщает последний достоверно сохраненный префикс, после кадра соединение закрывается. Неизвестный код закрывает соединение и требует чтения статуса без изменения принятого префикса у клиента. Если носитель больше не позволяет подтвердить ранее сохраненный префикс, состояние транзакции становится failed/reason=storageFault; успешный ACK запрещен. Непривязанный канал не может создать ERROR для действующей транзакции."
        },
        {
          "type": "p",
          "text": "Неполный заголовок либо DATA ДОЛЖЕН закончиться в пределах windowRoundTripMaxMs от первого байта этого кадра; при превышении приемник закрывает канал, не подтверждая хвост. Для TLS-handshake действуют общие сроки защищенного транспорта, а первый XML transfer.hello ДОЛЖЕН поступить полностью не позднее 10000 мс после handshake. Приемник ограничивает число незавершенных подключений, общий объем их памяти и частоту handshake по заранее заданным квотам; эти ресурсы не берутся из резерва управляющего канала. При отсутствии ожидающих данных общий и idle-сроки транзакции продолжают отсчитываться."
        },
        {
          "type": "heading",
          "id": "maintenance-bulk-persistence",
          "text": "Сроки, устойчивость и завершение передачи"
        },
        {
          "type": "p",
          "text": "Для B байт, размера блока S и окна W число блоков N=ceil(B/S), число расчетных окон K=ceil(N/W), transferBudgetMs=K*windowRoundTripMaxMs+600000. Все вычисления выполняются без переполнения. Бюджет свыше 604800000 мс отклоняется до резервирования. Это срок передачи при объявленных условиях, а не обещание скорости произвольной сети. Срок включает паузы без владения и переподключения; повтор begin/hello не обнуляет его. idleTimeoutMs=600000 считается от последнего впервые устойчиво сохраненного префикса. При истечении незакоммиченная передача удаляется, дальнейшее продолжение дает resultExpired."
        },
        {
          "type": "p",
          "text": "Получатель сохраняет ID, principalId, манифест, устойчивый префикс и расход бюджета атомарно с соответствующими данными до ACK. Последующее стирание/перезапись хвоста не разрушает подтвержденный префикс, даже если он делит физический erase-block с хвостом. Используются staging и журнал либо эквивалентная схема с проверяемым восстановлением; геометрия и износ носителя входят в паспорт реализации. НЕ ТРЕБУЕТСЯ отдельная синхронная запись метаданных на каждый DATA: ДОПУСКАЕТСЯ фиксация пакета принятых блоков в пределах окна и срока ACK. Действующий образ, конфигурация и опубликованный контент не перезаписываются приемом."
        },
        {
          "type": "p",
          "text": "Расход монотонного бюджета записывается до каждого подтверждения нового префикса и не реже одного раза в 60000 мс во время работы. После reboot к последнему сохраненному расходу добавляется 60000 мс; недостоверный журнал дает resultExpired. Время выключенного питания не входит в бюджет работы. Новый idle-срок не превышает min(600000,remainingBudgetMs). Перед продолжением сверяются целостность подтвержденных данных и журнал; несоответствие дает failed/storageFault, а не молчаливое уменьшение receivedBytes. Старые bootId/controlEpoch и файловые привязки не восстанавливаются."
        },
        {
          "type": "p",
          "text": "При receivedBytes=totalBytes content и firmware переходят receiving → complete; файловое соединение закрывается после последнего ACK. config переходит receiving → validating и проверяет длину, Стрибог-256, JSON-схему, ссылки и права за время не более 60000 мс, порциями до 100 мс; результат staged либо failed с reason. config.get transactionId возвращает receiving|complete|validating|staged|committed|cancelled|failed, receivedBytes/totalBytes/hash/remainingBudgetMs, progressBytes только в validating и reason только в failed. content.commit и firmware.verify выполняют свои проверки только после полного приема; content.commit при длительной проверке отвечает accepted с operation.id=upload.id, content.status показывает verifying/progressBytes и затем committed либо failed/reason. Проверка занимает не более 60000 мс и не задерживает управление. content.status содержит upload id/state=receiving|complete|verifying|committed|cancelled|failed/receivedBytes/totalBytes/remainingBudgetMs; failed требует reason, verifying — progressBytes."
        },
        {
          "type": "p",
          "text": "От полного приема до commit действует общий срок 600000 мс, включая проверку, потерю владения и ожидание. Переход в validating/verifying не начинает новый срок. Истечение удаляет только незакоммиченные данные. cancel ДОПУСКАЕТСЯ до атомарной фиксации и останавливает проверку; гонка cancel/commit имеет единственную точку решения и сохраненный итог. Для config/content отказ проверки оставляет прежнюю конфигурацию/ревизию действующей; failed не может стать committed повторным чтением. Ошибки проверки используют invalidArgument, conflict, invalidSignature, incompatible, rollbackDenied либо storageFault по предметной причине. При полном чтении нет обязательного ожидания EOF для перехода в complete/validating."
        },
        {
          "type": "heading",
          "id": "maintenance-verification-timers",
          "text": "Владение во время проверки полного файла"
        },
        {
          "type": "p",
          "text": "Переход в config.validating, content.verifying или firmware.verifying является активной ограниченной работой, а не ожиданием первого session.start. До начала работы УУ ДОЛЖНО проверить действующее maintenance-владение и атомарно установить внутренний verificationDeadlineMs=min(uptimeMs+60000, срок транзакции, срок после полного приема). Эти имена состояний обозначают соответствующую транзакцию; нового сетевого поля verificationDeadlineMs нет. На время проверки публикуемый ownership.idleDeadlineMs равен этому фиксированному сроку. Ранее установленный десятисекундный idle-срок не прерывает начатую проверку. Повтор команды, чтение статуса, heartbeat и изменение progressBytes НЕ ДОЛЖНЫ сдвигать verificationDeadlineMs. Перезапуск того же обработчика внутри действующей попытки также не начинает срок заново."
        },
        {
          "type": "p",
          "text": "Lease управляющего канала остается независимым: controlLeaseMs=30000, heartbeatIntervalMs=5000. Проверка файла сама не продлевает lease. Отзыв в пользу real, потеря эпохи или прав и cancel останавливают проверку на ближайшей границе сервисного шага до 100 мс; решение о real-владении и ответ остаются в пределах 500 мс. Превышение verificationDeadlineMs дает failed/reason=resultExpired и освобождает незакоммиченное хранилище; поздний обработчик не публикует результат. При совпадении срока и завершения сначала применяется истечение: атомарная фиксация результата разрешена только при uptimeMs<verificationDeadlineMs. Более ранний срок транзакции или срока после полного приема имеет тот же приоритет."
        },
        {
          "type": "p",
          "text": "После успешной проверки, отказа проверки либо cancel, если maintenance-эпоха еще действительна, начинается обычное ожидание: idleDeadlineMs=uptimeMs+10000, ограниченное оставшимся сроком незавершенной транзакции. Завершение проверки не освежает lease. Состояния complete, staged и verified сами по себе не удерживают владение. Сохраненный терминальный результат не удерживает staging и доступен по общим правилам хранения результата."
        },
        {
          "type": "p",
          "text": "При отзыве или потере maintenance-эпохи незавершенная validating/verifying прекращается на той же границе сервисного шага и возвращается в complete с сохраненными байтами и прежними сроками транзакции и после полного приема. Отложенный старый обработчик не фиксирует результат и не выполняет commit. config.get сообщает state=complete; повтор config.stage с тем же манифестом при новом владении снова запускает validating. Для content и firmware повторяются соответственно content.commit и firmware.verify новым обычным cmdSeq. Такая новая попытка получает собственные не более 60000 мс, ограниченные прежними сроками транзакции и после полного приема; эти общие сроки не продлеваются. Уже законченные staged/verified сохраняются; их commit заново проверяет текущую эпоху, права, off и применимость. Атомарно зафиксированный committed/activating не отменяется последующим отзывом и выясняется через статус."
        },
        {
          "type": "table",
          "headers": [
            "Проверка срока обслуживания",
            "Входные условия",
            "Обязательный исход"
          ],
          "rows": [
            [
              "bulk-verification-long",
              "Полный файл, действующая maintenance-эпоха; проверка длится 20000 мс, heartbeat через 5000 мс.",
              "config получает staged, firmware — verified, content — committed; прежний idle-срок 10000 мс не прерывает работу. После завершения idle не более 10000 мс."
            ],
            [
              "bulk-verification-deadline",
              "Проверка не завершилась за 60000 мс; heartbeat, чтения, повторы и progressBytes продолжаются.",
              "failed/resultExpired в установленный срок; эти события не сдвигают срок проверки. Завершение на самой границе срока уже опоздало."
            ],
            [
              "bulk-verification-lease",
              "Проверка дольше 30000 мс без допустимого продления lease владельцем.",
              "Истечение lease прекращает проверку на границе шага; файл возвращается в complete, старый обработчик не фиксирует результат."
            ],
            [
              "bulk-verification-real",
              "Допущенный acquire real при t=15000 мс незавершенной проверки.",
              "Сервис прекращается до t=15100, новая эпоха и ответ до t=15500; старое завершение не дает staged/verified/committed."
            ],
            [
              "bulk-verification-restart",
              "Отзыв эпохи в проверке; повтор под новым maintenance-владением, до общего срока остается 12000 мс.",
              "Повторная проверка получает не более оставшихся 12000 мс, не новые общие 600000 мс. Повтор в той же попытке не меняет ее срок."
            ]
          ]
        },
        {
          "type": "table",
          "headers": [
            "Расчетный пример",
            "Блок / окно / оборот окна",
            "Окна / бюджет"
          ],
          "rows": [
            [
              "1048576 байт",
              "4096 байт / 4 / 100 мс",
              "64 / 606400 мс"
            ],
            [
              "33554432 байт",
              "4096 байт / 4 / 100 мс",
              "2048 / 804800 мс"
            ],
            [
              "33554432 байт",
              "4096 байт / 4 / 600 мс",
              "2048 / 1828800 мс"
            ],
            [
              "33554432 байт",
              "65536 байт / 64 / 100 мс",
              "8 / 600800 мс"
            ]
          ],
          "caption": "Расчет бюджета передачи bulk/1"
        },
        {
          "type": "p",
          "text": "При 32 МиБ, S=4096, W=4 и обороте окна 100 мс расчетная передача занимает 204800 мс (3 мин 24,8 с) до общего запаса; при 600 мс — 1228800 мс (20 мин 28,8 с). Предельная полезная скорость в этой модели S*W/T составляет соответственно 163840 и примерно 27307 байт/с. В сравнении с 64 байтами и одним запросом при том же времени полного оборота количество последовательных ожиданий сокращается в 256 раз. Фактическая скорость ограничена также полосой, TLS и накопителем; паспорт не подменяет измерение. Более производительные изделия используют согласованные большие блоки и окна в тех же границах bulk/1."
        },
        {
          "type": "p",
          "text": "Кодировки ключа зависят от поля и не взаимозаменяемы. JSON x/y и publicX/publicY — по 64 строчных hex от 32-байтового целого big-endian. Отпечаток закрепляемого ключа вычисляется как Стрибог-256 от 04 || xBE[32] || yBE[32]. TLS key_share GC256A по RFC 9367, пункт 6.1.1.3 передает xLE[32] || yLE[32] без байта 04. Подпись передает rLE[32] || sLE[32]; координаты ключа не являются половинами подписи. Преобразование выполняется отдельно для каждого поля; разворот всего 64-байтового ключа меняет также порядок координат и запрещен. Векторы содержат все три представления и отпечаток одного тестового ключа."
        },
        {
          "type": "p",
          "text": "Подпись манифеста образа и пакета доверия кодируется одинаково с RFC 9367, пункт 5.3: r[32] || s[32], обе половины little-endian; полученные 64 байта представляются 128 строчными hex. Хеш трактуется как целое little-endian по модулю порядка подгруппы; ноль заменяется единицей. Старое прикладное s big-endian || r big-endian не принимается; автоматическое распознавание формата и DER-оболочка запрещены. Адаптер СКЗИ явно преобразует документированный выход своего API в этот формат ровно один раз. signature-vectors содержат JCS, хеш, GC256A, r/s, положительные подписи и отрицательные перестановки; прежний формат включен в отрицательные примеры."
        }
      ]
    },
    {
      "id": "integrations",
      "title": "15. Сторонние сервисы и расширения",
      "blocks": [
        {
          "type": "p",
          "text": "Внешние сервисы подключаются адаптерами к АРМ. Единая модель содержит предупреждение, ревизию, запуск, ветвь, область ресурсов, срок и происхождение. Для каждого адаптера задаются principalId, разрешенные операции, преобразование данных, дедупликация, подтверждения, таймауты и аудит."
        },
        {
          "type": "table",
          "headers": [
            "Вид интеграции",
            "Граница ответственности"
          ],
          "rows": [
            [
              "CAP / EDXL и обмен предупреждениями",
              "АРМ интерпретирует содержание, территорию, обновление и отмену. CAP не заменяет команды усилителю."
            ],
            [
              "REST, webhook, MQTT, очереди сообщений",
              "Способ доставки адаптера. Семантика повторов и происхождение не определяются названием транспорта."
            ],
            [
              "Телефония / SIP / источники речи",
              "АРМ проверяет право источника и преобразует поток в объявленный медиапрофиль."
            ],
            [
              "АСУ, диспетчеризация, мониторинг",
              "Отдельные права наблюдения и запуска, качество измерений, ограничение частоты и обратная доставка результата."
            ],
            [
              "ОУ другого производителя",
              "Согласованный с изготовителем ОУ интерфейс, каталог ресурсов, критерии исполнения и профиль испытаний."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Нельзя заранее гарантировать поддержку любого внешнего API. Расширяемость задается контрактом адаптера, версиями и capabilities. Неизвестная обязательная функция отклоняется целиком; расширение не может обходить право остановки или менять смысл существующего поля."
        },
        {
          "type": "p",
          "text": "УУ передает уведомления через журнал events.read с общими правилами курсора и качества. В управляющем профиле 0.20 инициативные изменяющие запросы УУ к АРМ запрещены. Адаптер внешнего сервиса получает самостоятельную идентичность и права; ответ УУ не является командой другому прибору."
        }
      ]
    },
    {
      "id": "verification",
      "title": "16. Проверки и критерии готовности",
      "blocks": [
        {
          "type": "note",
          "text": "Соответствие речевой части определяется для каждого объявленного режима: локальной записи, потока записи и микрофона. Критерии M01-M10 раздела 13 применяются к полному тракту от источника до выходов; прием команды и готовность декодера не заменяют критерии полноты сообщения и физического результата."
        },
        {
          "type": "p",
          "text": "Оценка соответствия выполняется по обязательным требованиям выбранного профиля и критериям настоящего раздела. Для каждого условия фиксируются входная последовательность, допустимые ответы, изменения состояния и физическое наблюдение. Повтор, перестановка, потеря сообщения и перезапуск проверяются как самостоятельные случаи. Отчеты и сведения об используемых средствах испытаний являются документами реализации."
        },
        {
          "type": "table",
          "headers": [
            "Проверка до выпуска",
            "Критерий"
          ],
          "rows": [
            [
              "Повтор / потерянный ответ / reconnect",
              "session.start не исполняется повторно; журнал результата отличает accepted от текущего running."
            ],
            [
              "Отмена до session.start и поздняя отмена",
              "Поздний session.start не выполняется, новый сеанс не останавливается старой отменой."
            ],
            [
              "Сеть, питание и зависшее ОУ",
              "stop/finish выполняются в заданной области; unknown не превращается в off; автоматический рестарт отсутствует."
            ],
            [
              "Датчики и опрос",
              "Устаревание, отсутствие, таймаут и авария различаются; полнота опроса не выдается за исправность."
            ],
            [
              "Парсер / транспорт / защита",
              "Разрывы записей, неверные длины, подделка, повторы и перегрузка не приводят к действию до полной проверки."
            ],
            [
              "Нагрузка",
              "Память, максимальный шаг цикла и задержка session.stop не превышают объявленные пределы при одновременном опросе, аудио и сервисной передаче."
            ],
            [
              "Питание при обновлении",
              "Загрузчик, конфигурация и контент восстанавливаются после обрыва питания в любой точке изменения на каждой объявленной аппаратной ревизии."
            ],
            [
              "Рисунок сирены",
              "Длительности фаз, начальная фаза, паузы, последний неполный цикл, пределы подготовки и выключения соответствуют одной редакции аппаратного профиля; одинаковый pattern не подменяет проверку одинакового сигнала."
            ],
            [
              "Границы таймеров",
              "Таймаут отправки после свежего продления lease только отсоединяет канал; stop/finish применяется при истечении оставшегося lease. Отзыв действующих полномочий проверяется отдельно и действует немедленно. Разрыв канала не сбрасывает lease; resume до и после его истечения различается; draining не допускает новый session.start. Повторы не продлевают подготовку, исполнение и остановку. При совпадении сроков действует заданный порядок обработки."
            ],
            [
              "Последовательные сеансы",
              "При подтвержденном off и действующих полномочиях переходы сирена -> запись и проверка -> оповещение укладываются в пределы паспорта при занятой истории. Испытываются все поддержанные переходы, максимальный всплеск и длительная заявленная частота, включая смену эпох. В пределах нагрузки нет busy только из-за истории; все прежние результаты доступны до истечения их срока. Переполнение за пределами нагрузки не вытесняет их и не вызывает воздействие."
            ],
            [
              "Хранение результата",
              "В одной загрузке результат доступен весь resultRetentionMs, в том числе после release; reboot дает staleBoot старому запросу и запускает восстановление по постоянному журналу без повторного session.start. Полный архив испытывается только при заявленном отдельном профиле."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Ресурсный бюджет определяется для целевого состава выпускаемой прошивки: код и загрузчик, конфигурация и ключи, постоянный журнал, пиковые SRAM/стек/SDRAM, сетевые соединения, криптография и тракт ОУ при одновременной нагрузке. Совместное размещение прежних протоколов и служебных приложений не является требованием открытого протокола. Первичное заводское программирование и последующее эксплуатационное обновление имеют отдельные процедуры; наличие первого не доказывает восстановление после сбоя второго. Емкость и сроки подтверждаются измерениями изделия, а не размером прежнего образа."
        },
        {
          "type": "heading",
          "id": "verification-additional",
          "text": "Дополнительная приемка"
        },
        {
          "type": "p",
          "text": "Для телеметрии обязательны сценарии изменения качества, пропуска событий, неполных снимков и запуска теста; для интеграции - происхождение, делегирование и частичный результат. Дополнительные сценарии U01-U12 устанавливают критерии взаимодействия АРМ и ОУ. Метод испытания ДОЛЖЕН сохранять проверяемые предпосылки, событие и ожидаемый результат."
        },
        {
          "type": "table",
          "headers": [
            "Дополнительная приемка",
            "Критерий"
          ],
          "rows": [
            [
              "U01-U03: этапы и речь",
              "Запоздавший session.stop сирены не выключает следующий этап; отмена в паузе запрещает повтор; устаревшее разрешение тангенты не открывает поток."
            ],
            [
              "U04-U06: доставка до ОУ",
              "Потеря подтверждения ОУ не вызывает второй session.start; резервный путь не дублирует неизвестный запуск; фазовые и общие показания разных вариантов ОУ не смешиваются."
            ],
            [
              "U07-U09: наблюдения",
              "Перестановка/повтор/пропуск страниц не меняют адресацию и полноту; активная до подключения авария видна без нового фронта; повтор эпизода или снимок не перезапускают сценарий."
            ],
            [
              "U10-U12: пределы и совместимость",
              "Таймаут программы не выдается за успешное окончание; два независимых АРМ одинаково трактуют профиль и отказ неизвестной функции; session.stop укладывается в измеренный бюджет под нагрузкой."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Приемка 0.20 включает: media.hello чужого principalId с верным mediaId во время звучания; повторную привязку того же владельца; поздний обработчик старого соединения; технический session.start с maxDurationMs=30001 и учебный session.start с maxDurationMs=1080001; тревогу во время короткого и длительного учения с проверкой независимого тракта; отказ записи результата после off; edge и level с активным входом при reboot; удержание уровня после законченной программы; снятие уровня до освобождения ресурса. Для level проверяются новый физический отсчет и новый sessionId; для edge отсутствие фронта запрещает запуск. Успешный снимок с failed-проверкой либо неподтвержденным off ДОЛЖЕН отклоняться независимо от счетчиков записей."
        }
      ]
    },
    {
      "id": "decisions",
      "title": "17. Контракт управляющего ядра",
      "blocks": [
        {
          "type": "p",
          "text": "Управляющий профиль open-alert-draft/0.20 задает конечное исполнение и единственного владельца. Форматы и межполевые ограничения настоящего раздела обязательны совместно с временной шкалой раздела 6 и правилами резервирования cmdSeq раздела 4. Разрешение функции определяется объявленными возможностями и полномочиями участника; синтаксически корректное сообщение не отменяет проверку состояния."
        },
        {
          "type": "heading",
          "id": "decisions-connection",
          "text": "Подключение"
        },
        {
          "type": "table",
          "headers": [
            "Шаг подключения",
            "Обмен"
          ],
          "rows": [
            [
              "1. Транспорт",
              "АРМ явно выбирает tls либо plain по настроенному адресу и порту УУ. Для tls профиль доверия известен при регистрации; прикладные команды до аутентификации не принимаются. Для plain ключи не требуются, но securityPolicy=secureOnly запрещает подключение."
            ],
            [
              "2. Начальный обмен",
              "Первый запрос: profile=bootstrap/1, action=protocol.hello, список 1-8 поддерживаемых профилей. УУ пересекает предложение клиента со своим разрешенным списком и выбирает профиль по собственному настроенному порядку приоритета; порядок клиента не влияет на выбор. Возвращаются selectedProfile, deviceId, security=tls|plain и bootId. Запрещенный локальной политикой профиль не участвует в выборе; клиент также проверяет свой разрешенный список. У ядра 0.20 единственный профиль open-alert-draft/0.20, другие профили допускаются только отдельными спецификациями. При отсутствии пересечения - incompatibleProfile и закрытие."
            ],
            [
              "3. Фиксация",
              "На данном соединении последующие запросы используют только selectedProfile. Повтор того же hello допустим до первой предметной команды и возвращает тот же выбор; смена списка требует нового соединения."
            ],
            [
              "4. Наблюдение",
              "device.describe/capabilities/state; АРМ проверяет kind, ресурсы, ревизии, часы uptimeMs и сроки профиля. Чтение не требует acquire."
            ],
            [
              "5. Управление",
              "acquire с сохраненными acquireSeq/instanceId, затем session.start. При потере канала - восстановление контекста и resume, без повторного физического запуска."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "decisions-start",
          "text": "Прием запуска"
        },
        {
          "type": "table",
          "headers": [
            "Поле session.start",
            "Тип и правило"
          ],
          "rows": [
            [
              "Оболочка",
              "v=1, profile, id, action=session.start, bootId, controlEpoch, cmdSeq обязательны. ID - 32 строчных hex, cmdSeq - uint64 > 0."
            ],
            [
              "session",
              "Ровно один элемент: mode=siren|recording|program|stream|test, purpose=real|exercise|technical, acceptBeforeMs, maxDurationMs, configRevision и capabilityRevision обязательны. Значений по умолчанию нет."
            ],
            [
              "acceptBeforeMs",
              "uint64 в монотонной шкале УУ данного bootId. session.start принимается только при uptimeMs < acceptBeforeMs <= uptimeMs + 30000. Повтор принятого session.start возвращает решение до повторной проверки срока."
            ],
            [
              "maxDurationMs",
              "Целое 100..86400000 мс от принятия, с подготовкой и паузами. Для test и purpose=technical верхняя граница 30000; для purpose=exercise — 1080000 с отдельными условиями допуска длительных учений. Исчерпание инициирует остановку с maxDurationExceeded."
            ],
            [
              "resources",
              "1-4 элемента resource: id длиной 1-32 ASCII-символа, bindingGeneration uint64. ID уникальны. Четыре ссылки любого режима с максимальными разрешенными длинами полей ДОЛЖНЫ помещаться в 1024 байтах компактного XML без отступов."
            ],
            [
              "siren",
              "Ровно один элемент siren: pattern=continuous|intermittent, stopMode=duration|command. Для duration обязателен durationMs=100..3600000 и durationMs <= maxDurationMs; для command durationMs запрещен. Рисунок сигнала определяется аппаратным профилем ниже; durationMs отсчитывается от running и включает паузы."
            ],
            [
              "recording",
              "Ровно один элемент recording: contentId, revision uint64, hashAlgorithm=streebog256 и hash из 64 строчных hex. Один проход; естественный конец отличается от maxDurationExceeded."
            ],
            [
              "test",
              "Ровно один элемент test: testId и testRevision uint64; purpose ДОЛЖНО быть technical. Эффекты и критерии заданы полным каталогом tests.list по разделу 10; testId/testRevision фиксируются до воздействия."
            ],
            [
              "program",
              "Ровно один program id/revision; полный неизменный план из programs.list, правила исполнения и результата раздела 24."
            ],
            [
              "stream",
              "Ровно один stream mediaId из audio.prepare; onControlLoss=stop, правила rtp-alert/4 раздела 13."
            ],
            [
              "Ревизии",
              "Все ресурсы, поколения и ревизии проверяются до воздействия. Их несовпадение дает conflict. Отсутствие обязательного поля не заменяется значением из старой карточки АРМ."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Порядок приема session.start: полное обрамление и лимиты; синтаксис и общая оболочка; аутентифицированные права и контекст; дедупликация/расходование cmdSeq; предметная схема и межполевые условия; актуальные ревизии и возможности; срок приема; занятость и готовность; сохранение решения accepted; передача задания исполнителю. После принятия физические отказы отражаются в результате сеанса. Утрата ответа не разрешает новый session.start с новым номером."
        },
        {
          "type": "p",
          "text": "АРМ рассчитывает acceptBeforeMs из свежего uptimeMs и конечного бюджета доставки, учитывая RTT, локальное прошедшее время и границу дрейфа. При ретрансляции исходный срок поручения не продлевается. Если соответствие шкал нельзя ограничить, запуск с таким сроком не принимается как своевременный. maxDurationMs и таймеры остановки не зависят от коррекции UTC."
        },
        {
          "type": "heading",
          "id": "decisions-siren",
          "text": "Физический рисунок сирены"
        },
        {
          "type": "table",
          "headers": [
            "Сирена: аппаратный профиль сигнала",
            "Обязательное правило"
          ],
          "rows": [
            [
              "continuous",
              "Непрерывная подача сигнала без программных пауз. Частотный состав, модуляция, разгон и выбег определяются опубликованным профилем ОУ; continuous не означает постоянное мгновенное значение акустического сигнала."
            ],
            [
              "intermittent",
              "Циклический рисунок из фаз сигнала и пауз. Профиль ДОЛЖЕН задавать их порядок и длительности, начальную фазу, допуски, поведение при остановке и точную редакцию нормативного описания, если он на него ссылается. Одного названия \"прерывистая сирена\" недостаточно."
            ],
            [
              "Исполнитель рисунка",
              "Профиль указывает, формирует ли рисунок само ОУ или драйвер УУ, чем подтверждается выбор рисунка и как начало его первой фазы связано с критерием running. Общий ответ ОУ \"режим сирены\" сам по себе не доказывает выбранный рисунок."
            ],
            [
              "Длительность и последний цикл",
              "При stopMode=duration durationMs - интервал от running, включая паузы. При command штатную остановку задает адресный session.stop. По его истечении начинается stopping; последний неполный цикл не продлевается до полного. Во время остановки новые фазы включения запрещены. Задержка физического выключения ограничена stopBeginMs/stopConfirmMs и критерием off профиля."
            ],
            [
              "Предел всего сеанса",
              "maxDurationMs идет от acceptedAtMs и имеет приоритет при одновременном истечении с durationMs. Для stopMode=duration АРМ оставляет бюджет подготовки сверх durationMs. Для command достижение maxDurationMs является защитным отказом; применяется onControlLoss=stop. Равенство durationMs=maxDurationMs допустимо по схеме, но не обещает полный интервал running или успешное завершение."
            ],
            [
              "Привязка до session.start",
              "При вводе в эксплуатацию АРМ и УУ получают одно и то же опубликованное описание сигнала с идентификатором и редакцией для каждого ресурса. Оно связано с deviceId, resourceId, bindingGeneration и принятыми configRevision/capabilityRevision. Смена рисунка меняет соответствующие ревизии; для активного сеанса профиль неизменен."
            ],
            [
              "Граница ядра",
              "session.start выбирает один из двух рисунков аппаратного профиля; произвольные длительности фаз в session.start не передаются. profiles.get предоставляет параметры и идентификатор описания сигнала. Полное физическое описание входит в паспорт изделия при вводе в эксплуатацию; автоматическое угадывание по одному pattern запрещено."
            ],
            [
              "Неподдержанное исполнение",
              "Допустимый XML не обещает любую длительность на любом ОУ. УУ ДОЛЖЕН отклонить неподдержанный рисунок или длительность с unsupported до воздействия. Молчаливое округление, замена рисунка или ожидание полного цикла сверх срока не допускаются; ограничения и допуски публикуются в профиле."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "decisions-read",
          "text": "Чтение и снимки"
        },
        {
          "type": "table",
          "headers": [
            "Чтение ядра",
            "Wire-контракт"
          ],
          "rows": [
            [
              "device.describe",
              "Запрос без дочернего элемента. Ответ device: deviceId, kind=vau|siren, version, uptimeMs, securityPolicy=optional|secureOnly; отдельный элемент info protocolName=\"Открытый протокол систем оповещения\". manufacturer/softwareDeveloper необязательны и обозначают фактического изготовителя и разработчика ПО. bootId в оболочке."
            ],
            [
              "device.capabilities",
              "Первая страница без параметров; продолжение selection snapshotId/cursor. capabilities содержит capabilityRevision и целые записи mode, command, resource, limit и mediaProfile. Конец каталога определяет more=false."
            ],
            [
              "device.state",
              "Первая страница без параметров; продолжение selection snapshotId/cursor. state содержит stateRevision, eventSeq, capabilityRevision, uptimeMs, resourceCount, alarmCount, sessionCount. Первая страница начинается ровно с configuration, ownership, recovery; продолжения их не повторяют. resource, session и alarm передаются целыми записями."
            ],
            [
              "inventory.get",
              "Необязательный selection с одним из pointId/resourceId/ouId; при продолжении фильтр сохраняется и добавляется snapshotId/cursor. inventory содержит capabilityRevision/bindingGeneration и целые записи point, enum, threshold, condition."
            ],
            [
              "sensors.read",
              "selection содержит 1-8 уникальных point id; при продолжении набор сохраняется. snapshot содержит capabilityRevision/bindingGeneration, stateRevision, eventSeq, evaluatedAtMs и complete."
            ],
            [
              "events.read",
              "events bootId/afterSeq; ответ events nextSeq/more/gap/oldestSeq и записи event. При gap=true записей нет, more=false, nextSeq=afterSeq; клиент получает новый снимок."
            ],
            [
              "Страницы",
              "Все контейнеры страниц, кроме events, содержат snapshotId и more; cursor обязателен только при more=true. Запрос продолжения содержит одновременно snapshotId/cursor. Снимок и критерии выборки неизменны, срок хранения снимка 30000 мс; после истечения - snapshotExpired."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Контекст чтения привязан к bootId, полученному bootstrap. Перезагрузка закрывает канал и требует нового bootstrap; явно переданный старый bootId получает staleBoot. Сводка session передается только на первой странице session.get; продолжения не повторяют ее. Принятые ревизии и время не меняются от чтения следующей страницы. Для чтения нового состояния начинается новый снимок."
        },
        {
          "type": "p",
          "text": "configuration обязательна на первой странице device.state независимо от поддержки config.get. configRevision - текущая сохраненная редакция, appliedRevision - фактически примененная, onControlLoss - действующая политика примененной редакции. При несовпадении редакций session.start получает unsafeState. Для первого session.start АРМ берет configRevision и capabilityRevision из согласованного состояния и повторно проверяет их при запуске; несвежая ревизия дает conflict/detail. Ревизия присваивается любой смене политики или параметров запуска. Разные appliedRevision и configRevision не дают АРМ права угадывать будущую политику."
        },
        {
          "type": "p",
          "text": "Ответ device.describe содержит ровно один device и один info с единственным атрибутом protocolName=\"Открытый протокол систем оповещения\". info не содержит наименования компании, рекламы или обязательной ссылки. Поле protocolDeveloper отсутствует в формате 0.20 и не является условием соответствия. Идентификаторы фактических manufacturer/softwareDeveloper необязательны. Условия свободного использования приведены в приложении Е; независимая реализация не обязана показывать сведения об издателе спецификации."
        },
        {
          "type": "p",
          "text": "ownership действующей эпохи дополнительно содержит purpose; при отсутствии сеанса - idleDeadlineMs. Снимок device.state фиксирует все ресурсы и текущий сеанс, а также полный набор незавершенных аварийных условий: active и unknown. Счетчики resourceCount/alarmCount/sessionCount относятся ко всему снимку, sessionCount равен 0 или 1. Ресурс, сеанс и авария передаются ровно один раз за все страницы. configuration/ownership/recovery передаются ровно один раз на первой странице. Метаданные контейнера, кроме more/cursor, повторяются без изменений. ownership в held/detached/draining содержит principalId и controlEpoch; free/released их не содержит; для blocked допустима либо эта пара, либо отсутствие обоих полей. Отсутствие аварии означает отсутствие актуального условия только после проверки последней страницы и всех счетчиков; частичное чтение не очищает аварии в АРМ."
        },
        {
          "type": "heading",
          "id": "decisions-alarms",
          "text": "Аварии"
        },
        {
          "type": "table",
          "headers": [
            "Запись alarm",
            "Нормативное правило"
          ],
          "rows": [
            [
              "Идентичность",
              "conditionId - устойчивый ID условия (token 1-32), pointId - точка каталога, alarmId - уникальный 32-hex ID эпизода. В снимке не более одного эпизода для conditionId; alarmId не используется повторно."
            ],
            [
              "Состояние и качество",
              "state=active|cleared|unknown, severity=info|warning|critical; validity, freshness, observedAtMs, changedAtMs, continuity обязательны. active/cleared требуют valid/fresh; потеря достоверного наблюдения дает unknown, а не cleared. Время - монотонные миллисекунды текущего bootId; changedAtMs <= observedAtMs."
            ],
            [
              "Снимок и журнал",
              "state содержит active/unknown. events.read: тип alarm.raised несет ровно один alarm state=active, alarm.cleared - state=cleared, alarm.qualityChanged - active/unknown того же эпизода, без подтверждения снятия. Изменение состояния и публикация события атомарно получают stateRevision и eventSeq. Подтвержденное снятие исключает эпизод из следующих снимков."
            ],
            [
              "Восстановление",
              "В одном bootId alarmId сохраняется при разрыве связи и переполнении журнала. После reboot достоверно восстановленный эпизод сохраняет ID; временные метки начинаются в новой шкале. При утрате доказанной преемственности создается новый ID с continuity=unknown, без утверждения о новом фронте. continuity=continuous требует сохраненного происхождения. Восстановленный снимок сам по себе не создает повторное внешнее действие."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "decisions-catalogue",
          "text": "Каталог точек"
        },
        {
          "type": "table",
          "headers": [
            "Запись каталога",
            "Обязательные и дополнительные поля"
          ],
          "rows": [
            [
              "point facet=identity",
              "id, facet, resourceId, ouId обязательны; parentId и sourceId необязательны."
            ],
            [
              "point facet=meaning",
              "id, facet, kind, type обязательны; unit и scale необязательны."
            ],
            [
              "point facet=source",
              "id, facet, sourceKind, presence, support обязательны. Поддержка не подменяет физическое наличие."
            ],
            [
              "point facet=acquisition",
              "id, facet, readMethod, maxAgeMs обязательны."
            ],
            [
              "enum",
              "pointId, code и label обязательны. Уникальность (pointId, code). Только для type=enum, как минимум одна запись. Строка label не более 64 декодированных байт UTF-8; этот предел учитывает расширение XML-сущностей при кодировании."
            ],
            [
              "threshold",
              "pointId, conditionId, operator, value, hysteresis, severity обязательны. Только int/counter; value и hysteresis - XML decimal без экспоненты, hysteresis >= 0. Порог сравнивает переданное числовое значение до применения scale. conditionId уникален в каталоге."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Полный каталог содержит четыре facet для каждой выбранной точки; enum и threshold не повторяют их поля. Записи связываются через pointId и общий snapshotId/capabilityRevision/bindingGeneration. Фильтр pointId/resourceId/ouId применяется ко всем частям описания, а не только к identity. Для порога gt/ge условие снимается при значении ниже value-hysteresis; для lt/le - выше value+hysteresis; eq/ne требуют hysteresis=0 и снимаются при ложном сравнении. Значения enum и пороги не являются командами воздействия."
        },
        {
          "type": "p",
          "text": "В ядре sensors.read возвращает полный выбранный набор: complete=true на всех страницах обозначает полноту зафиксированного набора, more=false - конец передачи. Недоступное показание передается записью с validity=unavailable и reason; не удаляется из набора. Неизвестный pointId отклоняет запрос с notFound. Запрошенная точка передается ровно один раз; посторонние точки запрещены. Изменение качества после фиксации снимка появляется в новом снимке или событии."
        },
        {
          "type": "p",
          "text": "Полнота определяется всей последовательностью запросов и ответов снимка. Обязательны первая страница без cursor, неизменные критерии выборки и метаданные, точная цепочка snapshotId/cursor, отсутствие циклов, повторов и лишних страниц, конечное more=false и равенство счетчиков/множества выбранных точек. Для session.get проверяются счетчики resourceCount/checkCount/observationCount/executionCount/faultCount, ссылки и условия успеха; сводка сеанса есть только на первой странице. Для inventory.get проверяются все facet и ссылки enum/threshold. Проверка одного ответа не доказывает полноту многостраничной передачи."
        },
        {
          "type": "p",
          "text": "Ниже приведен закрытый словарь значений профиля. Файл vocabulary-0.20.json содержит те же перечисления и коды ошибок. Произвольные значения вместо перечислений запрещены. conflict, forbidden и busy ДОЛЖНЫ содержать detail из conflictDetail, forbiddenDetail и busyDetail соответственно; прочие ошибки detail не передают. startBlockedByCancel ДОЛЖЕН содержать cancelSeq. Для failed/inconclusive/skipped/pending в check обязателен reason; skipped означает, что проверка не выполнялась. pending разрешен только до finished."
        },
        {
          "type": "heading",
          "id": "decisions-result",
          "text": "Результат сеанса"
        },
        {
          "type": "table",
          "headers": [
            "Результат session.get",
            "Контракт 0.20"
          ],
          "rows": [
            [
              "Контейнер result",
              "snapshotId, stateRevision, mode, evaluatedAtMs, resourceCount (1-4), checkCount, observationCount, executionCount, faultCount и more обязательны. cursor только при more=true. Счетчики относятся ко всему неизменному снимку."
            ],
            [
              "Сводка session",
              "Ровно одна на первой странице, отсутствует в продолжениях. Все принятые идентификаторы, ревизии, времена и onControlLoss сохраняются. Выбор по startSeq требует исходного controlEpoch."
            ],
            [
              "resource",
              "id, bindingGeneration, output, evidence, observedAtMs обязательны. Набор точно совпадает с принятым session.start; каждый ресурс передается один раз."
            ],
            [
              "check",
              "id, resourceId, phase=baseline|running|stopped, status обязательны; reason обязателен кроме passed. ID уникален во всем результате. pending означает ожидание выполнения; в finished запрещен."
            ],
            [
              "observation",
              "id, resourceId, pointId, phase, sourceId, sourceKind, type, validity, freshness, observedAtMs обязательны. value обязателен для valid; reason обязателен для invalid/unavailable. Всего не более 12 атрибутов."
            ],
            [
              "План activationPoll",
              "До воздействия tests.list фиксирует testId/testRevision, полный набор checks (id/resourceId/phase) и observations (id/resourceId/pointId/phase/type/sourceId/sourceKind), критерии и сроки. Для каждого ресурса предусмотрена хотя бы одна проверка. Наблюдения покрывают все три фазы для каждого ресурса. Все объявленные проверки и наблюдения этого теста обязательны для общего успеха."
            ],
            [
              "Неполученное наблюдение",
              "Запись сохраняет свой ID и фазу с validity=unavailable, freshness=never, observedAtMs=0 и reason. До исполнения check=pending; после завершения невыполненная проверка skipped. Исчезновение записи не заменяет отрицательный результат."
            ],
            [
              "Условия успеха",
              "succeeded требует off с допустимым evidence для всех ресурсов и passed для всех checks. Для test все observations также valid/fresh на момент соответствующей фазы. Свежесть исторического измерения здесь оценивается в фазе теста; чтение результата позже не превращает его в текущее показание. В finished причина completed требует succeeded, operatorStop допускает succeeded только по критериям подтвержденной работы раздела 6; в остальных случаях применяется cancelled либо unknown, maxDurationExceeded требует failed. Подтвержденное выключение после исчерпания предела не делает поручение выполненным."
            ],
            [
              "Проверка полноты",
              "Получатель проверяет страницы, счетчики и внутренние ссылки, сверяет результат с исходными session.start/accepted и фиксированным манифестом теста. Одни счетчики не доказывают, что сам отправитель не исключил ожидаемую проверку."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Для siren/recording/program/stream записи check и фазовые observation необязательны; resourceCount положителен. Для test каталог tests.list, полный набор критериев и фазовые наблюдения обязательны. Результат связывается с сохраненным accepted исходного session.start и его неизменным планом. Полный снимок содержит одну session, одну timeline и одну completion; execution и fault обязательны по счетчикам и правилам раздела 6."
        },
        {
          "type": "p",
          "text": "Первая страница state обязательно содержит configuration, ownership, recovery в этом порядке; первая страница result обязательно содержит session. Если предметные записи не помещаются, допускаются только этот обязательный набор и метаданные контейнера при more=true. Пустой контейнер вместо обязательного набора запрещен. Каждая следующая непоследняя страница ДОЛЖНА содержать полезную запись; сводка в продолжениях запрещена. УУ ДОЛЖЕН проверять кодируемость всех объявленных записей с оболочкой и cursor до объявления возможностей. Запись не дробится на произвольные фрагменты XML. Максимальные длины полей и расширение XML-сущностей учитываются при кодировании; session.start поддерживает четыре ресурса при лимите 1024 байта."
        },
        {
          "type": "heading",
          "id": "decisions-readings",
          "text": "Типы показаний"
        },
        {
          "type": "table",
          "headers": [
            "Тип показания",
            "Значение на проводе"
          ],
          "rows": [
            [
              "bool",
              "Только true или false."
            ],
            [
              "int",
              "Каноническое десятичное int64: -9223372036854775808..9223372036854775807; без плюса, ведущих нулей, -0 и экспоненты."
            ],
            [
              "counter",
              "Каноническое десятичное uint64: 0..18446744073709551615."
            ],
            [
              "enum",
              "Код token 1-32 из enum данного pointId в согласованном каталоге."
            ],
            [
              "type / scale",
              "Каждое показание содержит type, совпадающий с каталогом. scale - положительный десятичный множитель без экспоненты, до 32 ASCII-символов, при отсутствии 1. Отображаемое значение равно value * scale; пороги сравнивают исходное value."
            ]
          ]
        },
        {
          "type": "p",
          "text": "freshness=never означает отсутствие полученного измерения: value отсутствует, observedAtMs=0, reason обязателен. type обязателен и для sensor.qualityChanged. Получатель проверяет лексическую форму и диапазон, связывает полный каталог и readings по bootId/capabilityRevision/bindingGeneration, проверяет тип и принадлежность enum. Нельзя принять новый тип из показания как неявное изменение каталога. evidence имеет закрытый словарь: none, commandSent, reportedMode, electrical, acoustic, localConfirmation. none/commandSent не разрешают off. Допустимость остальных доказательств, их источник и свежесть определяются аппаратным профилем и не выводятся из одного имени."
        },
        {
          "type": "p",
          "text": "В снимке показаний observedAtMs ДОЛЖНО быть не больше evaluatedAtMs; changedAtMs, если присутствует, не больше observedAtMs. Для freshness=fresh возраст evaluatedAtMs - observedAtMs ДОЛЖЕН быть не больше maxAgeMs из acquisition каталога. Если показание повторяет maxAgeMs, значение ДОЛЖНО совпадать с каталогом. Получатель применяет maxAgeMs из каталога независимо от наличия этого атрибута в показании. Исторические observations результата проверяются относительно своей фазы, а не времени чтения результата."
        },
        {
          "type": "p",
          "text": "Для аварии observedAtMs относится к последнему полученному наблюдению источника. changedAtMs фиксирует последний подтвержденный источником переход состояния условия. Переход качества в unknown при потере источника не обновляет эти времена; его порядок задается eventSeq. Если наблюдений еще не было, оба времени равны 0 и freshness=never. Чтение кеша и пересчет качества не создают наблюдения. Подтверждение control.release ДОЛЖНО содержать исходный controlEpoch; leaseRemainingMs в channel.ping, acquire и resume ДОЛЖНО быть не больше 30000."
        },
        {
          "type": "heading",
          "id": "decisions-events",
          "text": "События"
        },
        {
          "type": "table",
          "headers": [
            "Событие ядра",
            "Ровно одно обязательное тело"
          ],
          "rows": [
            [
              "session.started / session.finished",
              "session со state=running / finished соответственно; завершение требует result/reason. Событие сообщает сводку, полный результат читается через session.get."
            ],
            [
              "sensor.qualityChanged / resource.changed",
              "point с обязательными типом и качеством / resource с output, evidence и observedAtMs."
            ],
            [
              "alarm.raised / alarm.cleared / alarm.qualityChanged",
              "alarm по правилам эпизода выше."
            ],
            [
              "inventory.changed",
              "Без дочерних записей; новая capabilityRevision в event требует перечитать каталог."
            ]
          ]
        },
        {
          "type": "p",
          "text": "session.execution и session.fault содержат execution и fault соответственно по разделу 17. Неизвестный тип события в ядре запрещен; poll.finished и прочие расширения требуют другого согласованного профиля. Курсор сдвигается только после проверки тела. Пустое session.finished не является допустимой квитанцией о завершении."
        },
        {
          "type": "p",
          "text": "При поддержке речи device.capabilities передает записи mediaProfile с id, security=tls|plain, codec=PCMA|GSM и packetMs. Одна запись задает один поддержанный вариант; ключ уникальности — все четыре поля. Для rtp-alert/4 PCMA требует 20 мс, GSM допускает 20/40/80; для group-audio/1 и group-audio-open/1 фиксированы GSM и 100 мс звука на часть, security соответственно tls и plain. Объявление command audio.group.prepare не заменяет объявления точного медиапрофиля. Необъявленное сочетание получает unsupported до выделения подготовки."
        },
        {
          "type": "heading",
          "id": "decisions-limits",
          "text": "Лимиты реализации"
        },
        {
          "type": "p",
          "text": "Начальный обмен выбирает точный профиль с фиксированными транспортными пределами; встречного предложения меньших лимитов в hello 0.20 нет. Ответ device.capabilities ДОЛЖЕН содержать все обязательные команды и каждое имя limit из таблицы ниже ровно один раз за полный каталог. Для постоянных значений ТРЕБУЕТСЯ точное совпадение. Прочие значения задают паспорт изделия и применимость развертывания, а не обещание универсальной емкости."
        },
        {
          "type": "table",
          "headers": [
            "limit name",
            "Единица",
            "Значение"
          ],
          "rows": [
            [
              "maxXmlBytes",
              "bytes",
              "1024"
            ],
            [
              "heartbeatIntervalMs",
              "ms",
              "5000"
            ],
            [
              "controlLeaseMs",
              "ms",
              "30000"
            ],
            [
              "frameAssemblyMs",
              "ms",
              "10000"
            ],
            [
              "writeProgressMs",
              "ms",
              "10000"
            ],
            [
              "ordinaryResponseMs",
              "ms",
              "500"
            ],
            [
              "cancelAdmissionMs",
              "ms",
              "100"
            ],
            [
              "snapshotLifetimeMs",
              "ms",
              "30000"
            ],
            [
              "maxSnapshots",
              "count",
              "1..18446744073709551615"
            ],
            [
              "maxSnapshotsPerPrincipal",
              "count",
              "1..18446744073709551615"
            ],
            [
              "snapshotMaxPages",
              "count",
              "1..18446744073709551615"
            ],
            [
              "snapshotReadMaxMs",
              "ms",
              "1..27000"
            ],
            [
              "eventCapacity",
              "count",
              "1..18446744073709551615"
            ],
            [
              "eventRateMax",
              "events/s",
              "1..18446744073709551615"
            ],
            [
              "eventBurstMax",
              "count",
              "0..18446744073709551615"
            ],
            [
              "eventDrainMinPerSec",
              "events/s",
              "1..18446744073709551615"
            ],
            [
              "stopBeginMs",
              "ms",
              "1..1000"
            ],
            [
              "stopConfirmMs",
              "ms",
              "1..10000"
            ],
            [
              "prepareTimeoutMs",
              "ms",
              "1..18446744073709551615"
            ],
            [
              "recoveryTimeoutMs",
              "ms",
              "1..18446744073709551615"
            ],
            [
              "resultRetentionMs",
              "ms",
              "30000..18446744073709551615"
            ],
            [
              "cmdSeqWindow",
              "count",
              "4096"
            ],
            [
              "technicalDurationMaxMs",
              "ms",
              "30000"
            ],
            [
              "exerciseDurationMaxMs",
              "ms",
              "1080000"
            ]
          ]
        },
        {
          "type": "p",
          "text": "stopBeginMs <= stopConfirmMs; maxSnapshotsPerPrincipal <= maxSnapshots. Квота учитывает все незавершенные снимки аутентифицированного участника, а не только одно соединение. При исчерпании квоты новый снимок получает busy; уже выданный живет 30000 мс от фиксации первой страницы без продления чтением. В пределах одной загрузки результат сеанса доступен не менее resultRetentionMs от finished независимо от срока конкретного снимка; активный сеанс не вытесняется. Новый session.start не сокращает эту гарантию и НЕ ДОЛЖЕН ожидать освобождения истории в пределах заявленной нагрузки. Емкость истории и последовательные переходы нормируются отдельно ниже. Гарантии постоянного журнала и чтения после reboot различаются по разделу 6. Кеш решений cmdSeq - отдельное хранилище."
        },
        {
          "type": "heading",
          "id": "decisions-history-capacity",
          "text": "История результатов и последовательные этапы"
        },
        {
          "type": "table",
          "headers": [
            "Параметр паспорта исполнения",
            "Обязательное требование"
          ],
          "rows": [
            [
              "Емкость истории",
              "УУ ДОЛЖЕН указать число одновременно сохраняемых полных результатов и срок хранения resultRetentionMs. До принятия session.start резервируется место его будущего результата. Снимки, кеш cmdSeq и постоянный журнал не заменяют эту емкость."
            ],
            [
              "Нагрузка последовательных сеансов",
              "Паспорт ДОЛЖЕН задать верхнюю границу числа завершений в любом скользящем окне длиной resultRetentionMs, с учетом всплесков, всех режимов, владельцев и смен эпох. Средней частоты без ограничения всплеска недостаточно. Емкость ДОЛЖНА покрывать эту границу и отдельно результат каждого одновременно принимаемого исполнения; в ядре такое исполнение одно. Если фактическое хранение дольше resultRetentionMs, невытесняемая часть истории также включается в расчет."
            ],
            [
              "Решение по следующему session.start",
              "Когда полномочия действуют, off подтвержден, обязательные проверки пройдены и нагрузка не превышена, занятая история сама по себе не допускает busy или ожидание истечения resultRetentionMs. Решение и постановка ответа на отправку укладываются в ordinaryResponseMs. Это еще не подтверждение начала звука."
            ],
            [
              "Время перехода между этапами",
              "Паспорт развертывания ДОЛЖЕН задать конечный допустимый интервал от подтверждения off предыдущего этапа до running следующего для каждого поддержанного перехода. Он включает получение результата и решение АРМ, разрешенную паузу программы, доставку следующего session.start, ordinaryResponseMs и подготовку до prepareTimeoutMs. Предел выбирается по требованиям сценария и подтверждается испытанием при заявленных канале и нагрузке; срок хранения истории не является допустимой добавочной паузой."
            ],
            [
              "Превышение заявленной нагрузки",
              "Если безопасно зарезервировать результат нельзя, session.start получает busy до принятия намерения и воздействия. УУ не сокращает гарантированное хранение и не вытесняет активный результат. Повтор того же cmdSeq возвращает прежнее решение; новая попытка требует нового номера и проверки состояния. АРМ учитывает отказ как нарушение сроков сценария, а не скрывает его неограниченным ожиданием."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Пример расчета емкости: при гарантированном пределе 60 завершений в любом окне 30000 мс, resultRetentionMs=30000 и одном принимаемом исполнении ТРЕБУЕТСЯ не менее 61 места. Число включает резерв результата уже принятого сеанса; это расчетный пример, а не обязательный размер для всех изделий. Параметры этой таблицы входят в опубликованный паспорт изделия и развертывания; новые XML-атрибуты limits редакцией документа не вводятся."
        },
        {
          "type": "heading",
          "id": "decisions-vocabulary",
          "text": "Машинный словарь"
        },
        {
          "type": "table",
          "headers": [
            "Тип",
            "Значения",
            "Смысл"
          ],
          "rows": [
            [
              "profile",
              "bootstrap/1 / open-alert-draft/0.20",
              "profile"
            ],
            [
              "bool",
              "true / false",
              "bool"
            ],
            [
              "mode",
              "siren / recording / test / program / stream",
              "mode"
            ],
            [
              "purpose",
              "real / exercise / technical",
              "purpose"
            ],
            [
              "policy",
              "stop / finish",
              "policy"
            ],
            [
              "phase",
              "starting / running / stopping / finished",
              "phase"
            ],
            [
              "output",
              "off / commandedOff / onObserved / unknown / fault",
              "output"
            ],
            [
              "result",
              "succeeded / failed / cancelled / unknown",
              "result"
            ],
            [
              "ownershipState",
              "free / held / detached / draining / blocked / released",
              "Состояние владения"
            ],
            [
              "recoveryState",
              "reconciling / ready / localInterventionRequired",
              "Восстановление после сбоя"
            ],
            [
              "checkStatus",
              "passed / failed / inconclusive / skipped / pending",
              "Итог проверки; pending только для незавершенного сеанса. Все статусы кроме passed требуют reason."
            ],
            [
              "presence",
              "present / absent / expectedMissing / unknown",
              "Физическое наличие точки"
            ],
            [
              "readMethod",
              "cached / refreshable / checkOnly",
              "Способ получения показания"
            ],
            [
              "sourceKind",
              "localInput / ouReply / controllerMetric / derived",
              "Источник данных"
            ],
            [
              "validity",
              "valid / invalid / unavailable",
              "Достоверность значения"
            ],
            [
              "freshness",
              "fresh / stale / never",
              "Свежесть наблюдения"
            ],
            [
              "facet",
              "identity / meaning / source / acquisition",
              "Часть описания точки"
            ],
            [
              "pointType",
              "bool / enum / int / counter",
              "Тип значения точки"
            ],
            [
              "support",
              "supported / unsupported",
              "Поддержка точки, отдельно от ее наличия"
            ],
            [
              "alarmState",
              "active / cleared / unknown",
              "Состояние аварийного эпизода"
            ],
            [
              "severity",
              "info / warning / critical",
              "Категория важности аварии"
            ],
            [
              "continuity",
              "continuous / unknown",
              "Подтвержденная непрерывность эпизода"
            ],
            [
              "thresholdOperator",
              "lt / le / gt / ge / eq / ne",
              "Сравнение значения с порогом"
            ],
            [
              "conflictDetail",
              "requestMismatch / configRevisionMismatch / capabilityRevisionMismatch / bindingChanged / contentRevisionMismatch / instanceMismatch / operationStateMismatch",
              "Машинная причина conflict"
            ],
            [
              "errorCode",
              "incompatibleProfile / staleAttachment / unsupportedVersion / unsupported / invalidArgument / forbidden / staleBoot / staleEpoch / busy / unsafeState / startWindowExpired / confirmationWindowExpired / startBlockedByCancel / conflict / resultExpired / notFound / snapshotExpired / cursorExpired / sourceUnavailable / storageFault / incompatible / incomplete / noSpace / invalidSignature / rollbackDenied",
              "Коды отказа приема; причины результата не являются кодами отказа"
            ],
            [
              "digestAlgorithm",
              "streebog256",
              "Стрибог-256 для содержимого и образов; 32 байта, 64 строчных hex-символа"
            ],
            [
              "evidence",
              "none / commandSent / reportedMode / electrical / acoustic / localConfirmation",
              "Уровень доказательства: нет, отправленная команда, режим ОУ, электрическое/акустическое наблюдение, местное подтверждение. off запрещен для none/commandSent."
            ],
            [
              "eventType",
              "session.started / session.finished / resource.changed / sensor.qualityChanged / inventory.changed / alarm.raised / alarm.cleared / alarm.qualityChanged / session.execution / session.fault",
              "События ядра; обязательное тело зависит от типа. poll.finished задается отдельным профилем."
            ],
            [
              "observationPhase",
              "baseline / running / stopped",
              "До воздействия, при воздействии, после остановки."
            ],
            [
              "limitName",
              "maxXmlBytes / heartbeatIntervalMs / controlLeaseMs / frameAssemblyMs / writeProgressMs / ordinaryResponseMs / cancelAdmissionMs / snapshotLifetimeMs / maxSnapshots / maxSnapshotsPerPrincipal / snapshotMaxPages / snapshotReadMaxMs / eventCapacity / eventRateMax / eventBurstMax / eventDrainMinPerSec / stopBeginMs / stopConfirmMs / prepareTimeoutMs / recoveryTimeoutMs / resultRetentionMs / cmdSeqWindow / technicalDurationMaxMs / exerciseDurationMaxMs",
              "Обязательные лимиты capabilities; значения и единицы заданы паспортом ядра. Неизвестные имена требуют другого согласованного профиля."
            ],
            [
              "resultReason",
              "completed / operatorStop / controlLostStop / maxDurationExceeded / prepareTimeout / stopDispatchTimeout / stopConfirmTimeout / sourceUnavailable / driverFault / storageFault / contentMismatch / mediaIncomplete / hardwareProtection / testFailed / stopUnconfirmed / observationLost / recoveryUncertain / mediaClockFault / equipmentFault / programAborted",
              "Закрытый словарь причин. programAborted допустим только у stageResult со state=notStarted и без result; у session и finished-этапа запрещен. Остальные пары result/reason заданы временной моделью."
            ],
            [
              "ownershipPurpose",
              "real / exercise / technical / maintenance",
              "Назначение эпохи; START совпадает с ним, maintenance не допускает START."
            ],
            [
              "executionStatus",
              "pending / confirmed / notExecuted / unknown",
              "Историческое подтверждение начала воздействия; confirmed не стирается остановкой или поздним отказом."
            ],
            [
              "completionState",
              "pending / complete / interrupted / unknown",
              "Полнота принятого задания отдельно от подтверждения начала."
            ],
            [
              "stopMode",
              "duration / command",
              "Сирена заданной длительности либо до штатной адресной остановки; maxDurationMs обязателен в обоих случаях."
            ],
            [
              "forbiddenDetail",
              "roleDenied / scopeDenied / ownerMismatch",
              "Машинная причина отказа по полномочиям; сведения о чужом исполнении не раскрываются."
            ],
            [
              "busyDetail",
              "ownershipHeld / executionActive / localControl / serviceActive / capacityExhausted",
              "Причина занятости; START не ставится в очередь."
            ],
            [
              "faultEffect",
              "none / degraded / failed / unknown",
              "Влияние диагностического условия на выбранное исполнение, независимо от важности severity."
            ],
            [
              "faultCode",
              "powerLost / phaseLost / batteryLow / lineOpen / lineShort / amplifierFault / ouLinkLost / outputMismatch / sourceLost / overTemperature / hardwareProtection / storageFault / mediaIncomplete / mediaClockFault / driverFault / sourceUnavailable / contentMismatch / maxDurationExceeded / prepareTimeout / stopDispatchTimeout / stopConfirmTimeout / testFailed / observationLost / vendorFault",
              "Код нарушения во время исполнения; конкретная область и влияние передаются отдельно."
            ],
            [
              "responseStatus",
              "ok / accepted / error",
              "responseStatus"
            ],
            [
              "testEffect",
              "none / readBus / switchOutput / audible / pauseAudio / resetInterface / writeStorage",
              "testEffect"
            ]
          ]
        },
        {
          "type": "heading",
          "id": "execution-wire",
          "text": "Записи подтверждения и полноты исполнения"
        },
        {
          "type": "table",
          "headers": [
            "Запись",
            "Обязательный контракт 0.20"
          ],
          "rows": [
            [
              "result",
              "Дополнительно executionCount=0..512 и faultCount=0..65535; неизменны на всех страницах. Ноль разрешен только для program без звучащих этапов; в остальных случаях executionCount=1..512. Вместе с остальными полями и cursor не более 12 атрибутов."
            ],
            [
              "execution",
              "resourceId, status, criterionId обязательны; stageIndex/passIndex только для program. При confirmed обязательны sourceId, evidence и observedAtMs; evidence не none/commandSent. При другом статусе evidence=none, sourceId/observedAtMs отсутствуют. Ключ — resourceId/stageIndex/passIndex, уникальный в снимке; resourceId принадлежит session.start."
            ],
            [
              "completion",
              "Ровно одна запись в полном session.get: state=pending|complete|interrupted|unknown. pending только до finished; complete означает достижение естественной границы всего сигнала, файла, потока, теста или программы, interrupted — подтвержденное прекращение до нее, unknown — полнота не доказана. Отдельно от execution и result; при operatorStop незавершенный файл имеет interrupted даже при succeeded. Для siren stopMode=command штатный session.stop после работы означает complete; при duration остановка до durationMs означает interrupted."
            ],
            [
              "fault",
              "id (32 hex), code, resourceId, effect, severity, occurredAtMs обязательны; conditionId и alarmId при каталожном условии, clearedAtMs только при подтвержденном снятии; stageIndex/passIndex только для program. Код берется из faultCode. Для внутреннего отказа контроллера resourceId=controller; это специальная область, не дополнительная цель session.start. ID уникален; записи сохраняются до конца хранения результата."
            ],
            [
              "executionCriterion в profiles.get",
              "resourceId, mode, criterionId, evidence, sourceId, maxAgeMs обязательны. Ровно один критерий для каждого поддержанного сочетания ресурс/режим; он связан с capabilityRevision/configRevision и описанием профиля. Evidence none ДОПУСКАЕТСЯ только при невозможности подтвердить начало и не разрешает confirmed. Условия для конкретной записи/потока, источника и свежести ДОЛЖНЫ быть опубликованы; критерий активного сеанса неизменен."
            ],
            [
              "События session.execution / session.fault",
              "Ровно одно тело execution / fault с дополнительным sessionId (32 hex). В session.get sessionId берется из единственной сводки и в этих записях не повторяется. Событие execution публикуется при первом confirmed; оно не означает finished."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Полнота session.get требует точных множеств execution согласно session.start/плану, одной completion, точных executionCount/faultCount и уникальных ключей. Положительный итог не выводится по первой странице. confirmed связывается с принятой ревизией, конкретным ресурсом и проходом; acceptedAtMs <= observedAtMs <= evaluatedAtMs, наблюдение относится к фазе работы данного прохода до stoppingAtMs. Историческая свежесть оценивается в момент подтверждения, а не чтения. Все необходимые доказательства off проверяются отдельно по resource и timeline."
        },
        {
          "type": "p",
          "text": "Сирена поддерживает два явно объявленных режима длительности: stopMode=duration с обязательным durationMs=100..3600000 либо stopMode=command без durationMs. Оба требуют конечного maxDurationMs. Отсутствие stopMode недопустимо. При command штатный конец задает session.stop; maxDurationExceeded остается защитным отказом. Для command применяется onControlLoss=stop независимо от настройки finish. В автономной program сирена всегда использует stopMode=duration; command внутри программы запрещен. Разрешенные stopMode объявляются параметрами profiles.get; неподдержанный вариант получает unsupported до воздействия."
        },
        {
          "type": "table",
          "headers": [
            "Уровень результата",
            "Закрытые значения и смысл"
          ],
          "rows": [
            [
              "Оболочка ответа УУ и node-alert/2",
              "ok — завершено синхронное действие; accepted — принято длительное действие; error — отказ команды. Принятие не доказывает физическое исполнение."
            ],
            [
              "Допуск цели admission",
              "pending|accepted|rejected|unknown — отдельная ось, а не второй словарь статусов оболочки."
            ],
            [
              "Ошибки bulk/1",
              "Коды 1..8 являются компактным кодированием имен общего словаря; соответствие bulkErrorCodes задано vocabulary и проверяется вместе с бинарными векторами. Значения не переиспользуются."
            ]
          ]
        }
      ]
    },
    {
      "id": "profiles",
      "title": "18. Семейство профилей и совместимость",
      "blocks": [
        {
          "type": "p",
          "text": "Протокол задает общую предметную модель и несколько профилей: АРМ-АРМ, АРМ-УУ, драйвер ОУ, медиа и обслуживание. Состояния, происхождение запуска и смысл результата едины; способы доставки и ресурсные пределы могут различаться. Устройство не обязано поддерживать все профили."
        },
        {
          "type": "table",
          "headers": [
            "Паспорт профиля",
            "Обязательное содержание"
          ],
          "rows": [
            [
              "Идентичность и версии",
              "profileId, версия схемы, зависимости от других профилей, правило совместимости и необходимые расширения."
            ],
            [
              "Предметный контракт",
              "Обязательные команды, состояния, ошибки, роли, ресурсы и критерии фактического результата."
            ],
            [
              "Пределы",
              "Байты сообщения, страницы, глубина, число контекстов, очереди, длительность операций и квоты. Ограничения имеют измеримое основание."
            ],
            [
              "Время канала",
              "Период heartbeat, порог потери владельца, таймер неполного кадра, таймер передачи, бюджет session.stop и условия восстановления. Учитываются RTT, потери и нагрузка."
            ],
            [
              "Доказательство совместимости",
              "Позитивные и негативные векторы, сценарии отказа, аппаратные ревизии и результат испытаний."
            ]
          ]
        },
        {
          "type": "p",
          "text": "После установления защищенного контекста стороны выбирают точный общий профиль с фиксированными лимитами. Если общего профиля нет, управление не начинается. Лимиты конкретного изделия читаются через capabilities и проверяются на применимость; hello не согласует их произвольное уменьшение. Нельзя молча сменить режим, отключить защиту или использовать неизвестное обязательное расширение. Согласование выполняется через protocol.hello профиля bootstrap/1 по разделу 17."
        },
        {
          "type": "p",
          "text": "Capabilities описывают поддержанные функции, согласованный профиль - разрешенный договор обмена, а readiness - возможность выполнить конкретный план сейчас. Эти три понятия различаются. Смена capabilityRevision или привязки требует повторной проверки применимости плана."
        },
        {
          "type": "heading",
          "id": "profiles-versions",
          "text": "Версии и совместимость"
        },
        {
          "type": "p",
          "text": "v=\"1\" обозначает оболочку сообщения и сохраняется независимо от номера веб-редакции. profile определяет точную схему и семантику; стороны ДОЛЖНЫ отвергать несовместимый профиль до воздействия. Примеры этой редакции используют open-alert-draft/0.20. Изменение схемы или обязательного поведения получает новый идентификатор профиля. Реализация ДОЛЖНА соответствовать выбранному контракту целиком."
        },
        {
          "type": "p",
          "text": "Матрица каталога различает обязательные команды ядра и дополнительные расширения. Если расширение объявлено, реализация ДОЛЖНА выполнять весь его контракт и зависимости: например, tests.list для mode=test, семейство poll.start/get/cancel для заданий опроса и config.stage/commit/cancel для транзакционной настройки. Отклонение неподдержанного режима является штатным результатом поддержанной команды session.start."
        },
        {
          "type": "p",
          "text": "Для независимых производителей публикуются обязательное ядро и отдельные опциональные профили, реестр имен/ошибок, схемы, тестовые векторы и правила неизвестных полей. Обязательные расширения согласуются до исполнения. Необязательные расширения допускаются только в определенных схемой местах; неизвестный элемент не становится произвольной командой. Таблица совместимости включает разные АРМ и драйверы ОУ, ограниченные функции, обновление версий и отрицательные случаи. Закрытые особенности конкретного АРМ не являются условием базовой совместимости."
        },
        {
          "type": "heading",
          "id": "profiles-connection",
          "text": "Соединение и проверка устройства"
        },
        {
          "type": "p",
          "text": "Для базового постоянного TCP-канала запросы, ответы и события идут в обе стороны по этому соединению; отдельный обратный сокет НЕ ТРЕБУЕТСЯ самим фактом отправки события. Очереди и корреляция направлений остаются ограниченными. Последовательность команд одного УУ не запрещает АРМ параллельно обслуживать другие приборы."
        },
        {
          "type": "p",
          "text": "АРМ ДОЛЖЕН проверить описание устройства, его функциональный тип и объявленные возможности до запуска. Типы ВАУ и Сирена задают назначение ОУ; разрешение конкретного режима определяется capabilities и полномочиями участника. Один и тот же ОУ имеет одного действующего владельца управления."
        },
        {
          "type": "p",
          "text": "busy является штатным отказом при занятости другим сеансом или владельцем. Получатель ответа сохраняет действующее исполнение; новый запрос запуска не становится отложенным заданием, которое включит ОУ после освобождения."
        },
        {
          "type": "heading",
          "id": "profiles-normative",
          "text": "Изменение обязательного поведения"
        },
        {
          "type": "p",
          "text": "Идентификатор управляющего контракта этой редакции - open-alert-draft/0.20. Любое изменение обязательного поведения, диапазона, значения по умолчанию, правил дедупликации, полномочий, таймеров или достаточности результата требует нового идентификатора профиля, даже при неизменном XML. Редакция текста при прежнем профиле может исправлять опечатки, ссылки и пояснения, не меняющие множество допустимых трасс. Выбор через protocol.hello фиксирует весь контракт; автоматическое смешивание правил разных профилей запрещено."
        }
      ]
    },
    {
      "id": "registry",
      "title": "19. Логические цели, реестр и маршрутизация",
      "blocks": [
        {
          "type": "table",
          "headers": [
            "Сущность",
            "Смысл"
          ],
          "rows": [
            [
              "targetId",
              "Непрозрачный постоянный 128-битный ID логической точки оповещения в АРМ. Регион и муниципалитет не зашиваются в его байты."
            ],
            [
              "deviceId",
              "Идентичность физического УУ; в tls подтверждается ключом, в plain криптографически не подтверждена. Замена прибора меняет эту идентичность."
            ],
            [
              "Привязка",
              "targetId -> ответственный nodeId, deviceId, ouId/resourceId, bindingGeneration и registryRevision."
            ],
            [
              "Локальный locator",
              "Область сети, транспорт, адрес и порт. Это способ добраться до прибора, а не основание его полномочий."
            ],
            [
              "Маршрут между АРМ",
              "Разрешенные соседние узлы, версия пути, hopLimit и исходный срок приема. Происхождение поручения сохраняется."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Два одинаковых частных IP в разных областях сети допустимы. Команды и результаты различаются по логической цели, идентичности прибора и области маршрутизации. Два несовместимых назначения одного targetId или одного физического ресурса образуют конфликт, который выявляется до автоматического применения."
        },
        {
          "type": "p",
          "text": "Вышестоящий АРМ адресует разрешенную цель; нижестоящий разрешает ее через свой реестр. УУ проверяет локальную ссылку ouId/resourceId и поколение привязки. Полный реестр всей системы в малом контроллере не хранится. Перенос цели на замененный УУ выполняется явно, с отзывом старого назначения, без переноса активного сеанса."
        },
        {
          "type": "note",
          "text": "Если старое ОУ не умеет удостоверять собственный ID, identityEvidence=commissionedBinding означает подтвержденную при наладке привязку. Криптографическая идентичность Ethernet-части не доказывает правильность подключения исполнительного тракта или идентичность неаутентифицированного усилителя."
        },
        {
          "type": "p",
          "text": "Циклы маршрутов запрещены; hopLimit уменьшается на каждом переходе и не превышает 16. Межузловое поручение содержит deadlineUtcMs и clockErrorMs; uint64 передаются десятичными строками. Каждый узел допускает поручение только при upperBoundUtcMs < deadlineUtcMs, где upperBoundUtcMs - его текущее UTC плюс установленная верхняя погрешность не более 500 мс. Неизвестная погрешность либо превышение 500 мс дает clockUncertain без воздействия. Посредник сохраняет исходный deadlineUtcMs и не выдает новый полный TTL. АРМ переводит оставшийся гарантированный срок в acceptBeforeMs шкалы УУ с учетом RTT, погрешности и прошедшего локального времени."
        },
        {
          "type": "heading",
          "id": "registry-node-wire",
          "text": "Межузловой формат node-alert/2"
        },
        {
          "type": "p",
          "text": "АРМ—АРМ использует отдельное TLS 1.3-соединение с криптографическим профилем раздела 12, индивидуальным PSK пары узлов и заранее разрешенными nodeId. Адрес, port и networkScope задаются реестром при вводе; multicast и открытый запасной канал запрещены. Рамка uint32 BE длины и UTF-8 JSON: 1..65536 байт, глубина до 12, не более 4096 узлов JSON, повтор ключа запрещен. Тело соответствует node-schema-0.20.json, неизвестные поля отвергаются. Первый запрос node.hello; hello.nodeId сверяется с TLS principalId, ответ hello фиксирует node-alert/2. Срок handshake 20000 мс от приема TCP; сборка/отправка кадра — 20000 мс при нижнем ориентире канала 64 кбит/с. Кадр максимальной длины 65536 байт занимает 8192 мс без транспортных расходов и не подчиняется пределу 1024 байта конечного УУ; предел обработки обычного запроса 500 мс, длительное исполнение возвращает accepted. Чтение не поддерживает владение УУ."
        },
        {
          "type": "p",
          "text": "В таблицах каждое поле занимает отдельную строку. Знак ? после имени обозначает необязательное поле; остальные поля обязательны. Варианты объекта приведены отдельно. Целые в строке передаются в канонической десятичной записи; обе границы диапазона включены. Предел в байтах UTF-8 применяется к декодированному значению; непарные суррогаты ЗАПРЕЩЕНЫ. Для массива указаны минимальное и максимальное количество элементов и тип элемента. Дополнительные межполевые условия приведены в правилах соответствующего профиля."
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-node-hello-1",
          "caption": "Тело запроса node.hello",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "nodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "profile",
              "Точное значение: \"node-alert/2\""
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-run-start-1",
          "caption": "Тело запроса run.start",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "deadlineUtcMs",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "clockErrorMs",
              "Целое в строке: 0..500"
            ],
            [
              "hopLimit",
              "Целое в строке: 1..16"
            ],
            [
              "path",
              "Массив 1..16: Строка: 32 символа [0-9a-f]; без повторов"
            ],
            [
              "purpose",
              "Строка; одно из: real, exercise"
            ],
            [
              "planId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "planRevision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "maxDurationMs",
              "Целое в строке: 100..86400000"
            ],
            [
              "targetDigest",
              "Строка: 64 символа [0-9a-f]"
            ],
            [
              "targets",
              "Массив 1..256: selectionTarget"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-run-cancel-1",
          "caption": "Тело запроса run.cancel",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "deadlineUtcMs",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "clockErrorMs",
              "Целое в строке: 0..500"
            ],
            [
              "hopLimit",
              "Целое в строке: 1..16"
            ],
            [
              "path",
              "Массив 1..16: Строка: 32 символа [0-9a-f]; без повторов"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-run-get-1",
          "caption": "Тело запроса run.get — вариант 1",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-run-get-2",
          "caption": "Тело запроса run.get — вариант 2",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "snapshotId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "cursor",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-result-read-1",
          "caption": "Тело запроса result.read — вариант 1",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "targetId",
              "Строка: 32 символа [0-9a-f]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-result-read-2",
          "caption": "Тело запроса result.read — вариант 2",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "targetId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "snapshotId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "cursor",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-catalog-get-1",
          "caption": "Тело запроса catalog.get — вариант 1",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "—",
              "Пустой объект {}"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-catalog-get-2",
          "caption": "Тело запроса catalog.get — вариант 2",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "snapshotId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "cursor",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-request-observations-get-1",
          "caption": "Тело запроса observations.get",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "targetId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "pointIds",
              "Массив 1..8: Строка: 1..32 символа [A-Za-z0-9_.:-]; без повторов"
            ],
            [
              "deadlineUtcMs",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "clockErrorMs",
              "Целое в строке: 0..500"
            ],
            [
              "hopLimit",
              "Целое в строке: 1..16"
            ],
            [
              "path",
              "Массив 1..16: Строка: 32 символа [0-9a-f]; без повторов"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-hello-1",
          "caption": "Объект ответа hello",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "nodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "selectedProfile",
              "Точное значение: \"node-alert/2\""
            ],
            [
              "maxBytes",
              "Точное значение: \"65536\""
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-admission-1",
          "caption": "Объект ответа admission",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "state",
              "Строка; одно из: accepted, cancelled"
            ],
            [
              "targetCount",
              "Целое в строке: 0..256"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-targetresult-1",
          "caption": "Объект ответа targetResult",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "targetId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "admission",
              "Строка; одно из: pending, accepted, rejected, unknown"
            ],
            [
              "state",
              "Строка; одно из: pending, starting, running, stopping, finished, notStarted"
            ],
            [
              "result",
              "Строка; одно из: none, succeeded, failed, cancelled, unknown"
            ],
            [
              "reason",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "execution",
              "Строка; одно из: pending, confirmed, partial, notExecuted, unknown"
            ],
            [
              "completion",
              "Строка; одно из: pending, complete, interrupted, unknown"
            ],
            [
              "sourceNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "deviceId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "bootId?",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "sessionId?",
              "Строка: 32 символа [0-9a-f]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-runresult-1",
          "caption": "Объект ответа runResult",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "snapshotId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "evaluatedAtUtcMs",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "more",
              "Логическое значение: true или false"
            ],
            [
              "cursor?",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "targetCount",
              "Целое в строке: 0..256"
            ],
            [
              "state",
              "Строка; одно из: pending, running, finished"
            ],
            [
              "targets",
              "Массив 0..32: targetResult"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-rawresult-1",
          "caption": "Объект ответа rawResult",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "originNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "originSeq",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "runId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "branchId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "targetId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "sourceNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "snapshotId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "more",
              "Логическое значение: true или false"
            ],
            [
              "cursor?",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "requestXml",
              "Строка: не более 1024 символов"
            ],
            [
              "responseXml",
              "Строка: не более 1024 символов"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-catalog-1",
          "caption": "Объект ответа catalog",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "snapshotId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "more",
              "Логическое значение: true или false"
            ],
            [
              "cursor?",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "targetCount",
              "Целое в строке: 0..256"
            ],
            [
              "groupCount",
              "Целое в строке: 0..128"
            ],
            [
              "programCount",
              "Целое в строке: 0..128"
            ],
            [
              "targets",
              "Массив 0..32: target"
            ],
            [
              "groups",
              "Массив 0..32: group"
            ],
            [
              "programs",
              "Массив 0..32: program"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-observation-1",
          "caption": "Объект ответа observation",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "type",
              "Строка; одно из: bool, enum, int, counter"
            ],
            [
              "value?",
              "Строка: не более 128 байт UTF-8"
            ],
            [
              "validity",
              "Строка; одно из: valid, invalid, unavailable"
            ],
            [
              "freshness",
              "Строка; одно из: fresh, stale, never"
            ],
            [
              "reason?",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "sourceId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "observedAtMs",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "evaluatedAtMs",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "maxAgeMs",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "validUntilUtcMs?",
              "Целое в строке: 0..18446744073709551615"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-observations-1",
          "caption": "Объект ответа observations",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "targetId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "sourceNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "bootId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "capabilityRevision",
              "Целое в строке: 0..18446744073709551615"
            ],
            [
              "bindingGeneration",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "points",
              "Массив 1..8: observation"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-error-1",
          "caption": "Объект ответа error — code=\"forbidden\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "code",
              "Точное значение: \"forbidden\""
            ],
            [
              "detail",
              "Строка; одно из: roleDenied, scopeDenied, ownerMismatch"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-error-2",
          "caption": "Объект ответа error — code=\"busy\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "code",
              "Точное значение: \"busy\""
            ],
            [
              "detail",
              "Строка; одно из: ownershipHeld, executionActive, localControl, serviceActive, capacityExhausted"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-error-3",
          "caption": "Объект ответа error — code=\"conflict\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "code",
              "Точное значение: \"conflict\""
            ],
            [
              "detail",
              "Строка; одно из: requestMismatch, configRevisionMismatch, capabilityRevisionMismatch, bindingChanged, contentRevisionMismatch, instanceMismatch, operationStateMismatch"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "node-response-error-4",
          "caption": "Объект ответа error — вариант 4",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "code",
              "Строка; одно из: invalidArgument, unsupported, notFound, resultExpired, snapshotExpired, clockUncertain, deadlineExpired, routeLoop, sourceUnavailable, storageFault, startBlockedByCancel"
            ]
          ]
        },
        {
          "type": "p",
          "text": "Оболочка запроса: v=\"1\", profile=\"node-alert/2\", id (32 hex), action и body. Оболочка ответа: v/profile/to/status/kind/body; to равен id. kind определяет запись из таблицы; error имеет status=error, admission - accepted, остальные - ok. node.hello отвечает hello, run.start/run.cancel - admission, run.get - runResult, result.read - rawResult, catalog.get - catalog, observations.get - observations. Каждое чтение использует новый id; повтор изменения с тем же смысловым ключом допускает новый транспортный id. Один обычный запрос без ответа на соединении; run.cancel передается по отдельному TLS-соединению той же пары с собственным hello и не ожидает очереди обычного канала. Предел такого соединения - одно на пару; оно допускает только hello и cancel."
        },
        {
          "type": "p",
          "text": "run.start принимает только неизменную локальную программу planId/planRevision и purpose=real|exercise; поток микрофона и технические тесты не транслируются этим профилем. maxDurationMs:100..86400000; для exercise не более 1080000 с выполнением правил допуска длительных учений. targets фиксируют точный набор и bindingGeneration, без дубликатов, сортируются по targetId; targetDigest - Стрибог-256 от JCS этого массива. Группа раскрывается отправителем в targets до команды; изменение группы не меняет принятую ветвь. Все цели одной ветви относятся к одному конечному ownerNodeId и одному маршруту; несколько конечных узлов получают разные branchId/originSeq одного runId. Посредник пересылает ветвь целиком, не создает скрытых целей и не меняет planRevision/deadline. Конечный АРМ сохраняет ветвь перед session.start УУ и сопоставляет каждому target его собственные bootId/controlEpoch/startSeq/sessionId."
        },
        {
          "type": "p",
          "text": "originNodeId задает уполномоченного автора; значение проверяется по локальному разрешению пары TLS principalId/originNodeId/область целей/действие на каждом переходе. Это доверие к уполномоченным посредникам, не сквозная цифровая подпись origin. path начинается с originNodeId, заканчивается отправляющим узлом и не содержит получателя. При пересылке узел добавляет свой ID, уменьшает hopLimit на 1; прием требует hopLimit:1..16, clockErrorMs:0..500 и UTC с известной погрешностью <=500 мс. deadlineUtcMs ДОЛЖЕН быть позже локального верхнего UTC и не дальше 86400000 мс; уменьшение hopLimit до 0 запрещает следующий переход. Цикл дает routeLoop. Смена пути после accepted не разрешает запуск той же цели новой ветвью: неизвестный предыдущий исход сначала выясняется или адресно отменяется."
        },
        {
          "type": "p",
          "text": "originSeq - положительный uint64, сохраняемый автором до отправки, возрастающий для каждой новой ветви; переполнение запрещает новые ветви, но не их отмену. Каждый принимающий узел хранит постоянный highWater для originNodeId и решения активных ветвей. Новый run.start требует originSeq>highWater; выдача accepted, сохранение тела и highWater атомарны до пересылки. Предметный отказ новой ветви после проверки прав и оболочки также сохраняется и расходует номер; при отказе самого постоянного журнала воздействия запрещены. Точный повтор получает сохраненное решение; другое тело при том же номере - conflict. Вытесненное решение при номере <=highWater дает resultExpired, без повторного воздействия. АРМ сериализует прием новых номеров по каждому маршруту; перестановка новых ветвей может дать отказ старому номеру, но не поздний запуск."
        },
        {
          "type": "p",
          "text": "run.cancel использует originSeq отменяемой ветви, а не новый номер. При известной ветви runId/branchId/originNodeId обязаны совпасть; отмена адресуется только ее исполнениям через session.stop/session.cancel УУ. При неизвестном номере >highWater сначала атомарно сохраняются highWater и запрет позднего run.start (startBlockedByCancel проверяется прежде сравнения тела session.start); ответ cancelled с targetCount=0 не означает физического выключения. Для номера <=highWater без истории возвращается resultExpired; это не отменяет уже сохраненного запрета повторного запуска. Активная ветвь и ее отмена не вытесняются. Отмену вправе передать только первоначальный непосредственный делегат той же origin либо сам origin по разрешенному прямому маршруту. Повторы отмены не двигают сроки. Срок отмены определяет ее допуск; принятая остановка продолжается после его истечения."
        },
        {
          "type": "p",
          "text": "Решение ветви и полный итог хранятся не менее 86400000 мс после finished; highWater сохраняется без срока и переживает reboot. Утрата или повреждение этого журнала запрещает новые run.start, но не чтение диагностики и восстановление собственных активных ресурсов. Снимки run.get/catalog.get/result.read живут 30000 мс; continuation передает snapshotId/cursor, ответ more=true требует cursor, more=false запрещает его. Каждая непоследняя страница содержит запись; пустой каталог или отмененная до допуска ветвь имеют единственную страницу more=false; сводные числа/метаданные неизменны, каждая ожидаемая цель передается ровно раз. targetCount/groupCount/programCount относятся ко всему снимку. Неизвестная ссылка, чужая цель, повтор записи или утрата страницы не дают частичного общего успеха."
        },
        {
          "type": "p",
          "text": "runResult фиксирует каждую цель из session.start. Обязательные execution и completion сохраняют раздельные факты: confirmed означает подтверждение всех ожидаемых воздействий, partial — лишь части, notExecuted — доказанное отсутствие всех, unknown — недостаток доказательств, pending — ожидание. Для program пауза не подтверждает звучание. completion переносит полноту исходного результата; посредник не выводит complete из succeeded/operatorStop. Полные execution/fault и источник доказательств читаются через result.read. До решения по цели обе сводки pending; при отказе до воздействия execution=notExecuted, completion=interrupted. pending имеет result=none; notStarted требует admission=rejected и result=failed, либо admission=unknown/result=unknown при утрате доказательств. starting/running/stopping требуют admission=accepted и result=none. finished требует admission=accepted, sessionId/bootId и терминальный result/reason раздела 6. В неисполненных состояниях sessionId отсутствует. sourceNodeId и deviceId берутся из зафиксированного реестра. Общий state=finished означает отсутствие незавершенных целей, а не общий успех; успех возможен лишь при succeeded каждой ожидаемой цели. reason=none используется только при result=none; причина отказа допуска - код ошибки профиля. Таймаут канала не создает finished/succeeded."
        },
        {
          "type": "p",
          "text": "result.read возвращает исходную пару XML request/response session.get от конкретного УУ, каждую не более 1024 байт UTF-8, с исходными bootId, ссылками, временем и качеством. Внешний snapshotId/cursor принадлежит сохраненной коллекции, внутренний XML не переписывается. Получатель проверяет полный исходный снимок по правилам АРМ—УУ; одно сообщение не доказывает его полноту. catalog.get передает замороженный разрешенный каталог targets/groups/programs схемы конфигурации; отсутствие записи не отзывается частичной страницей. Объявленные квоты ДОЛЖНЫ позволять кодировать одну полную запись и считывать весь снимок за 27000 мс."
        },
        {
          "type": "p",
          "text": "observations.get возвращает ровно запрошенные 1..8 точек целевого УУ одним ответом; неподдержанная цель дает notFound, недоступная точка - unavailable/never/reason с observedAtMs=0 без value. Тип и enum берутся из полного каталога УУ; valid требует value, invalid/unavailable требуют reason и запрещают value. Ответ сохраняет исходные bootId, evaluatedAtMs и observedAtMs. fresh дополнительно требует validUntilUtcMs: консервативную нижнюю оценку срока свежести, рассчитанную конечным узлом по исходному maxAgeMs, возрасту, задержке доставки и погрешности UTC. Посредники не увеличивают этот срок. При неизвестной границе задержки или UTC свежесть не заявляется. Получатель считает значение fresh только пока его верхнее UTC не превысило validUntilUtcMs. Получение сообщения само по себе не освежает данные."
        },
        {
          "type": "p",
          "text": "XML управления, JSON конфигурации и строка исходного XML в result.read обслуживают разные границы. result.read сохраняет исходное доказательство УУ: JSON-экранирование снимается ровно один раз, полученная UTF-8 строка проверяется как отдельный кадр XML до 1024 байт с обычными ограничениями глубины, элементов и сущностей. XML не исполняется как команда, внешние сущности и DTD запрещены. Для JSON отдельно действуют пределы внешнего кадра и глубины; они не заменяют внутренние. Хеш исходного XML, если используется вне этого профиля, относится к исходным байтам, а не к их JSON-экранированию. Контроллер УУ не обязан реализовывать межузловой JSON-парсер."
        }
      ]
    },
    {
      "id": "readiness",
      "title": "20. Готовность плана и проверка записей",
      "blocks": [
        {
          "type": "p",
          "text": "readiness.get отвечает на вопрос о готовности конкретного режима, набора ресурсов и версии контента. Ответ основан на наблюдениях и не включает выходы, не запускает скрытый функциональный тест, не резервирует ресурс и не дает права управления."
        },
        {
          "type": "table",
          "headers": [
            "Результат",
            "Правило оценки"
          ],
          "rows": [
            [
              "blocked",
              "Есть обязательное препятствие: например, ресурс занят, выключение неизвестно, контент не совпадает или функция не поддерживается."
            ],
            [
              "unknown",
              "Обязательных доказательств недостаточно либо они устарели; отсутствие данных не считается нормой."
            ],
            [
              "degraded",
              "Обязательные условия выполнены, но есть ухудшение, которое явно разрешено данным профилем и политикой."
            ],
            [
              "ready",
              "Все обязательные условия на момент оценки выполнены; допустимых ухудшений нет. Это не гарантия будущего звучания."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Приоритет сводки: blocked, затем unknown, затем degraded, иначе ready. По каждому ресурсу сохраняются reason, обязательность проверки, источник, возраст и срок применимости доказательства. Отсутствие необязательного акустического датчика отражает глубину наблюдаемости и само по себе не блокирует все запуски."
        },
        {
          "type": "p",
          "text": "Оценка связывается с mode, выбранными ресурсами, contentId/revision/hash, bootId, bindingGeneration, configRevision, capabilityRevision и stateRevision. session.start повторяет быстрые обязательные проверки актуального контекста. Изменение внутреннего интерфейса, ресурса или записи после ready не позволяет использовать устаревшее заключение как разрешение."
        },
        {
          "type": "note",
          "text": "Предпусковая оценка не включает звучащий тест. Обязательные текущие доказательства исправности и off имеют возраст не более 1000 мс; решение readiness формируется не более чем за 500 мс. Отсутствие необязательного акустического датчика допускает degraded только при выполнении заявленного обязательного электрического или цифрового критерия. Права, ревизии, подлинность контента и неизвестное состояние выхода обходить запрещено. Универсальной команды force нет."
        },
        {
          "type": "heading",
          "id": "readiness-content",
          "text": "Готовность содержимого"
        },
        {
          "type": "p",
          "text": "content.describe возвращает манифест записи: contentId, revision, hashAlgorithm/hash, формат, длительность, размер, место хранения/исполнения, состояние проверки и ее возраст. Ожидаемый хеш манифеста и хеш реально проверенных байтов различаются. Номер слота и имя файла не подтверждают содержание."
        },
        {
          "type": "p",
          "text": "Если интерфейс ОУ не позволяет проверить байты встроенной записи, verified не выдается. Фоновая проверка или отдельная сервисная работа ДОЛЖНЫ иметь конечный бюджет; чтение описания не запускает скрыто полное хеширование. Необходимая проверка перед session.start либо уже достоверна, либо дает понятную причину отказа/неопределенности в пределах бюджета. Текст и видео остаются отдельными возможностями и не следуют из типа ВАУ или Сирена."
        },
        {
          "type": "p",
          "text": "readiness.get: запрос plan с mode=siren|recording|program|stream|test, configRevision, capabilityRevision; resources и ровно один элемент режима с теми же полями, что session.start, но без acceptBeforeMs. Для оценки длительности обязателен maxDurationMs в plan. Ответ readiness - неизменный снимок с snapshotId, evaluatedAtMs, validUntilMs=evaluatedAtMs+1000, stateRevision, more/cursor. Первая страница содержит summary state=ready|degraded|blocked|unknown; записи resource содержат id/bindingGeneration/state/reason. Записи evidence содержат resourceId/pointId/required/observedAtMs/validUntilMs/validity. Продолжение: selection snapshotId/cursor, исходный план фиксируется снимком. Оценка не резервирует ресурс."
        },
        {
          "type": "p",
          "text": "content.describe: selection contentId/revision либо snapshotId/cursor. Ответ contents: snapshotId, capabilityRevision, more/cursor. Для каждой записи content передаются id/revision/bytes/sampleCount/codec/hashAlgorithm/hash/location; location=controller|ou. Отдельная запись verification с contentId/revision, state=verified|unknown|invalid, observedAtMs, sourceId и actualHash при доступных байтах. verified требует совпадения actualHash с hash той же неизменной ревизии; unknown не содержит actualHash. Для локальной программы УУ допускает только verified и удерживает неизменные байты до завершения исполнения."
        }
      ]
    },
    {
      "id": "cascade-results",
      "title": "21. Каскадная диагностика и групповой отчет",
      "blocks": [
        {
          "type": "p",
          "text": "АРМ может запросить наблюдения через несколько подчиненных узлов, имея отдельное право чтения в указанной области targetId. Запрос имеет operationId и срок. Каждый посредник сохраняет источник, bootId, качество и возраст исходного доказательства; время получения посредником не превращает старое наблюдение в fresh."
        },
        {
          "type": "p",
          "text": "Итог оповещения связан с runId/branchId, planRevision и неизменным набором целей с ревизией/хешем состава. Для каждой цели разделяются принятие команды, исполнение и физическое наблюдение. Подтверждение промежуточного узла не означает срабатывания всех его ОУ."
        },
        {
          "type": "table",
          "headers": [
            "Текущее состояние цели",
            "Значение"
          ],
          "rows": [
            [
              "pending / active",
              "Ожидание решения / принятое незавершенное исполнение."
            ],
            [
              "succeeded / failed",
              "Установленный успех по объявленному критерию / установленная ошибка. Критерий и доказательство доступны отдельно."
            ],
            [
              "cancelled / notStarted",
              "Подтвержденная отмена исполнения / установленный факт отсутствия старта в данной ветви."
            ],
            [
              "unknown",
              "Доступные данные не позволяют установить исход. Потеря связи не превращается автоматически в failed или notStarted."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Состояния взаимоисключающие и в сумме покрывают весь зафиксированный набор получателей. Счетчики переходов хранятся отдельно. Повтор той же ревизии отчета не увеличивает итоги; позднее доказательство создает новую ревизию и сохраняет аудит. Два маршрута к одному физическому ресурсу не дают двух успехов."
        },
        {
          "type": "p",
          "text": "Сводка раскрывается до страниц исходных результатов с учетом прав наблюдателя. Недоступная ветвь сохраняется в expected-наборе с неизвестным результатом. Отменяющий ответ без исторических данных не доказывает notStarted. Старая привязка не сливается с новой по совпадению IP."
        },
        {
          "type": "note",
          "text": "Групповой запуск не является атомарной транзакцией. Политика частичной готовности задается до запуска. Остановка уже исполняемых ветвей по этой политике возможна только в своей области полномочий; приоритет или соседняя неисправность не дают права прервать чужое оповещение."
        }
      ]
    },
    {
      "id": "commissioning",
      "title": "22. Регистрация и согласованная конфигурация",
      "blocks": [
        {
          "type": "p",
          "text": "Обнаружение сообщает кандидата, регистрация устанавливает доверие и назначение. Discovery опционально, ограничено разрешенной областью, временем и квотой; статическая настройка остается возможной. Найденный IP, имя и объявленные возможности не дают устройству права управления."
        },
        {
          "type": "table",
          "headers": [
            "Этап",
            "Проверяемое действие"
          ],
          "rows": [
            [
              "Допуск",
              "Проверка идентичности УУ, уникальности ID, роли, права регистрации и привязки конкретного ОУ."
            ],
            [
              "Импорт",
              "Пакет содержит schemaVersion, configId/revision, целевые ID, профили, группы, сценарии, манифесты контента и происхождение. Рабочие ключи и идентичность не клонируются."
            ],
            [
              "Валидация",
              "До применения проверяются ссылки, коллизии, права, циклы, неподдержанные функции, отсутствующие записи и лимиты."
            ],
            [
              "Согласование групп",
              "Уполномоченный автор области, groupRevision и digest состава. Неизвестная ревизия не заменяется одноименной локальной группой."
            ],
            [
              "Применение",
              "Раздельные desired, saved и applied, причина расхождения и ожидаемый перезапуск. Ответ на SET не заменяет чтения фактического состояния."
            ],
            [
              "Замена оборудования",
              "Новая физическая идентичность и привязка, отзыв прежнего назначения, проверяемый перенос совместимых параметров. Активные сеансы не переносятся."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Распределенное применение пакета возвращает applied/rejected/pending/unknown по каждому элементу и позволяет повторное согласование. Оно не является одной атомарной транзакцией всех устройств. Откат проходит валидацию как новое разрешенное изменение и не отменяет исторически выполненные физические действия."
        },
        {
          "type": "p",
          "text": "Пакет конфигурации - UTF-8 JSON с обязательными schemaVersion=\"open-alert-config/5\", configId, revision, authorId, targets, groups, programs, content, profiles, signalSpecs, controllers и localRules. ID и uint64 представлены строками, повтор ключа запрещен. Для digest применяется Стрибог-256 от JCS по RFC 8785; перед канонизацией множества и вложенные коллекции сортируются по правилам схемы open-alert-config/5 ниже; порядок этапов сохраняется. authorId связывается с аутентифицированным правом изменения области, а не принимается на доверии из файла. Импорт не переносит PSK, deviceId, bootId или текущие контексты владения."
        },
        {
          "type": "p",
          "text": "Применение нового configRevision сохраняет состояние эпизода неизменного localRule.id/revision. Смена постороннего параметра не взводит вход заново и не повторяет запуск. Любое изменение самого правила требует увеличения его revision; новый набор проверяется с правами на указанный purpose до commit. Для получателя-УУ пакет обязан содержать controller с собственным deviceId и его локальные цели; отсутствие собственной политики не заменяется значением по умолчанию."
        },
        {
          "type": "p",
          "text": "open-alert-config/5 содержит обязательные коллекции controllers и localRules. controller.id равен deviceId; каждому deviceId из targets соответствует ровно одна запись controllers. onControlLoss задает сохраненную политику real-сеансов этого УУ. Для exercise/technical и сетевой речи действует stop независимо от этой настройки. Пустая localRules явно отключает локальные запуски; отсутствие любой обязательной коллекции отклоняет пакет. Политики не кодируются произвольными именами profiles.parameters."
        },
        {
          "type": "p",
          "text": "localRule.id и revision идентифицируют правило и его редакцию; deviceId/inputId связывают его с существующим физическим bool-входом каталога этого УУ. Для пары deviceId/inputId ДОПУСКАЕТСЯ одно правило. activeValue задает активную полярность; trigger=edge|level определяет поведение раздела 25. debounceMs и rearmMs: 1..60000; programId/programRevision ссылаются на неизменную локальную программу пакета. targetIds: 1..4 различные цели того же deviceId; сеть и цели другого УУ для автономного правила запрещены. purpose=real|exercise требует соответствующего разрешения регистрации источника. maxDurationMs: 100..86400000, для exercise не более 1080000 с выполнением правил допуска длительных учений; это полный срок вместе с подготовкой и паузами. Допустимость программы, наличие локального контента и физические границы остановки проверяются до commit. Неизвестный вход, неподтвержденная привязка, отсутствующая редакция или недостаточные полномочия отклоняют весь пакет."
        },
        {
          "type": "p",
          "text": "controllers сортируются по id, localRules по id, localRule.targetIds по строке ID; повтор запрещен. Смена конфигурации не является фронтом входа. Новое или измененное правило edge сначала ожидает устойчивой неактивности; level получает новое наблюдение после атомарного применения конфигурации. Изменение или удаление правила с выполняющимся либо неопределенным сеансом запрещено. Пакет атомарно сохраняет политику потери управления и весь набор локальных правил вместе с configRevision."
        },
        {
          "type": "heading",
          "id": "commissioning-schema",
          "text": "Закрытая схема конфигурации open-alert-config/5"
        },
        {
          "type": "p",
          "text": "config-schema-0.20.json является нормативной JSON Schema Draft 2020-12. Все объекты закрыты: неизвестное поле, null вместо значения, повтор ключа, неканоническое число или превышение uint64 отклоняет весь пакет до stage. Строки без Unicode-нормализации, предел текстового значения 128 байт UTF-8; ID/token и максимальные количества заданы схемой. Пакет не более 1048576 байт после нормализации и JCS. Пределы maxItems каждой коллекции и общего размера действуют одновременно: их произведение не является обещанной емкостью одного пакета. Набор с допустимыми размерами отдельных коллекций, но превышающий общий размер, недопустим. Автор вычисляет точную длину JCS до передачи; config.stage с bytes выше предела получает invalidArgument до выделения staging. Общий пакет не разбивается на независимые частичные commit. Машинный параметр x-maxJcsBytes требует отдельной проверки длины канонических байтов, поскольку стандарт JSON Schema не измеряет сериализованный размер. Отдельная машинная схема и таблицы полей описывают один формат."
        },
        {
          "type": "p",
          "text": "В таблицах каждое поле занимает отдельную строку. Знак ? после имени обозначает необязательное поле; остальные поля обязательны. Варианты объекта приведены отдельно. Целые в строке передаются в канонической десятичной записи; обе границы диапазона включены. Предел в байтах UTF-8 применяется к декодированному значению; непарные суррогаты ЗАПРЕЩЕНЫ. Для массива указаны минимальное и максимальное количество элементов и тип элемента. Дополнительные межполевые условия приведены в правилах соответствующего профиля."
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-root-1",
          "caption": "Объект root",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "schemaVersion",
              "Точное значение: \"open-alert-config/5\""
            ],
            [
              "configId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "authorId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "targets",
              "Массив 0..256: target"
            ],
            [
              "groups",
              "Массив 0..128: group"
            ],
            [
              "programs",
              "Массив 0..128: program"
            ],
            [
              "content",
              "Массив 0..256: content"
            ],
            [
              "profiles",
              "Массив 0..256: profile"
            ],
            [
              "signalSpecs",
              "Массив 0..256: signalSpec"
            ],
            [
              "mediaGroups",
              "Массив 0..128: mediaGroup"
            ],
            [
              "controllers",
              "Массив 0..256: controller"
            ],
            [
              "localRules",
              "Массив 0..128: localRule"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-mediagroup-1",
          "caption": "Объект mediaGroup — security=\"tls\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "deviceId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "sourcePrincipalId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "security",
              "Точное значение: \"tls\""
            ],
            [
              "sourceAddress",
              "Строка: адрес IPv4"
            ],
            [
              "sourcePort",
              "Целое в строке: 1..65535"
            ],
            [
              "groupAddress",
              "Строка: адрес IPv4"
            ],
            [
              "destinationPort",
              "Целое в строке: 1..65535"
            ],
            [
              "ttl",
              "Целое в строке: 1..255"
            ],
            [
              "sourceKeyId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "publicX",
              "Строка: 64 символа [0-9a-f]"
            ],
            [
              "publicY",
              "Строка: 64 символа [0-9a-f]"
            ],
            [
              "resources",
              "Массив 1..4: Объект mediaGroup.resources[]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-mediagroup-2",
          "caption": "Объект mediaGroup — security=\"plain\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "deviceId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "sourcePrincipalId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "security",
              "Точное значение: \"plain\""
            ],
            [
              "sourceAddress",
              "Строка: адрес IPv4"
            ],
            [
              "sourcePort",
              "Целое в строке: 1..65535"
            ],
            [
              "groupAddress",
              "Строка: адрес IPv4"
            ],
            [
              "destinationPort",
              "Целое в строке: 1..65535"
            ],
            [
              "ttl",
              "Целое в строке: 1..255"
            ],
            [
              "resources",
              "Массив 1..4: Объект mediaGroup.resources[]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-mediagroup-resources-1",
          "caption": "Объект mediaGroup.resources[]",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "resourceId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "bindingGeneration",
              "Целое в строке: 1..18446744073709551615"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-signalspec-1",
          "caption": "Объект signalSpec",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "kind",
              "Строка; одно из: tone, linearSweep, mechanical"
            ],
            [
              "waveform",
              "Строка; одно из: sine, square, mechanical"
            ],
            [
              "minHz",
              "Целое в строке: 1..20000"
            ],
            [
              "maxHz",
              "Целое в строке: 1..20000"
            ],
            [
              "toleranceHz",
              "Целое в строке: 0..19999"
            ],
            [
              "periodMs",
              "Точное значение: \"0\" ИЛИ Целое в строке: 100..60000"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-target-1",
          "caption": "Объект target",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "ownerNodeId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "deviceId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "resourceId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "bindingGeneration",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "networkScope",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "address",
              "Строка: адрес IPv4"
            ],
            [
              "port",
              "Целое в строке: 1..65535"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-group-1",
          "caption": "Объект group",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "members",
              "Массив 1..256: Строка: 32 символа [0-9a-f]; без повторов"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-siren-1",
          "caption": "Объект siren",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "pattern",
              "Строка; одно из: continuous, intermittent"
            ],
            [
              "durationMs",
              "Целое в строке: 100..3600000"
            ],
            [
              "signalProfileId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "signalProfileRevision",
              "Целое в строке: 1..18446744073709551615"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-recording-1",
          "caption": "Объект recording",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "contentId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "hashAlgorithm",
              "Точное значение: \"streebog256\""
            ],
            [
              "hash",
              "Строка: 64 символа [0-9a-f]"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-pause-1",
          "caption": "Объект pause",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "durationMs",
              "Целое в строке: 0..60000"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-stage-1",
          "caption": "Объект stage — kind=\"siren\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "index",
              "Целое в строке: 1..8"
            ],
            [
              "kind",
              "Точное значение: \"siren\""
            ],
            [
              "repeat",
              "Целое в строке: 1..16"
            ],
            [
              "gapMs",
              "Целое в строке: 0..60000"
            ],
            [
              "siren",
              "siren"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-stage-2",
          "caption": "Объект stage — kind=\"recording\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "index",
              "Целое в строке: 1..8"
            ],
            [
              "kind",
              "Точное значение: \"recording\""
            ],
            [
              "repeat",
              "Целое в строке: 1..16"
            ],
            [
              "gapMs",
              "Целое в строке: 0..60000"
            ],
            [
              "recording",
              "recording"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-stage-3",
          "caption": "Объект stage — kind=\"pause\"",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "index",
              "Целое в строке: 1..8"
            ],
            [
              "kind",
              "Точное значение: \"pause\""
            ],
            [
              "repeat",
              "Точное значение: \"1\""
            ],
            [
              "gapMs",
              "Точное значение: \"0\""
            ],
            [
              "pause",
              "pause"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-program-1",
          "caption": "Объект program",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "stages",
              "Массив 1..8: stage"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-placement-1",
          "caption": "Объект placement",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "targetId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "location",
              "Строка; одно из: controller, ou"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-content-1",
          "caption": "Объект content",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "codec",
              "Строка; одно из: PCMA, GSM"
            ],
            [
              "bytes",
              "Целое в строке: 1..33554432"
            ],
            [
              "sampleCount",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "hashAlgorithm",
              "Точное значение: \"streebog256\""
            ],
            [
              "hash",
              "Строка: 64 символа [0-9a-f]"
            ],
            [
              "placements",
              "Массив 1..256: placement"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-parameter-1",
          "caption": "Объект parameter",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "name",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "type",
              "Строка; одно из: bool, enum, int, counter, string"
            ],
            [
              "value",
              "Строка: не более 128 байт UTF-8"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-profile-1",
          "caption": "Объект profile",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "kind",
              "Строка; одно из: vau, siren"
            ],
            [
              "targetIds",
              "Массив 1..256: Строка: 32 символа [0-9a-f]; без повторов"
            ],
            [
              "parameters",
              "Массив 1..64: parameter"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-controller-1",
          "caption": "Объект controller",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "onControlLoss",
              "Строка; одно из: stop, finish"
            ]
          ]
        },
        {
          "type": "table",
          "layout": "schema",
          "id": "config-schema-localrule-1",
          "caption": "Объект localRule",
          "headers": [
            "Поле",
            "Тип и допустимые значения"
          ],
          "rows": [
            [
              "id",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "revision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "deviceId",
              "Строка: 32 символа [0-9a-f]"
            ],
            [
              "inputId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "trigger",
              "Строка; одно из: edge, level"
            ],
            [
              "activeValue",
              "Строка; одно из: true, false"
            ],
            [
              "debounceMs",
              "Целое в строке: 1..60000"
            ],
            [
              "rearmMs",
              "Целое в строке: 1..60000"
            ],
            [
              "programId",
              "Строка: 1..32 символа [A-Za-z0-9_.:-]"
            ],
            [
              "programRevision",
              "Целое в строке: 1..18446744073709551615"
            ],
            [
              "purpose",
              "Строка; одно из: real, exercise"
            ],
            [
              "maxDurationMs",
              "Целое в строке: 100..86400000"
            ],
            [
              "targetIds",
              "Массив 1..4: Строка: 32 символа [0-9a-f]; без повторов"
            ]
          ]
        },
        {
          "type": "p",
          "text": "В каждой коллекции id уникален; каждая цель связана не более чем с одним физическим профилем. Targets описывают адресацию существующих ресурсов, а не меняют deviceId прибора. port:1..65535; bindingGeneration/revision положительны. Тройка ownerNodeId/deviceId/resourceId не повторяется. IPv4 используется только в указанной networkScope; авторизованный реестр связывает ownerNodeId с доверенным узлом. group.members ссылаются только на targets этого пакета, вложенных групп и циклов нет. Все profile.targetIds и content.placements.targetId также существуют в targets; placement не повторяет targetId. Программа ссылается на content.id/revision/hash и signalProfileId/revision из того же пакета; несогласованность отклоняет весь пакет. Профили, требуемые программой, ДОЛЖНЫ быть применимы ко всем выбранным целям session.start."
        },
        {
          "type": "p",
          "text": "stage.index последовательно 1..число этапов; repeat:1..16, gapMs:0..60000. pause требует repeat=1/gapMs=0 и durationMs:0..60000; siren.durationMs:100..3600000. В config/5 длительность этапа всегда конечная; stopMode в JSON не передается, при экспорте XML programs.list указывается stopMode=duration. Размер content.bytes:1..33554432, кодек и sampleCount удовлетворяют правилам локального файла раздела 13. parameter.name уникален в профиле; bool имеет true/false, int - int64, counter - uint64, enum - token, string - строку до 128 байт. Обязательные параметры физического паспорта и межполевые ограничения раздела 8 проверяются сверх структуры JSON. Импорт манифеста не присваивает verified: фактические байты и наблюдения проверяются независимо."
        },
        {
          "type": "p",
          "text": "Нормализация множеств перед JCS: targets/groups/programs/content/profiles/signalSpecs/controllers/localRules сортируются по id, members и targetIds - по строке ID, placements - по targetId, parameters - по name. Порядок stages сохраняется; индекс сверяется с позицией, перестановка не исправляется автоматически. УУ принимает по config.stage только полученные канонические байты; их хеш совпадает с хешем сохраненного пакета. configId идентифицирует линию конфигурации, revision возрастает; одинаковая пара с другими байтами дает conflict. authorId ДОЛЖЕН совпадать с разрешенным автором аутентифицированного principalId. Общий пакет содержит полное локальное состояние, а не неявный patch; удаление занятого объекта запрещено."
        }
      ]
    },
    {
      "id": "scale-profile",
      "title": "23. Паспорт масштаба и бюджета времени",
      "blocks": [
        {
          "type": "p",
          "text": "Системная емкость не вычисляется из размера адресного поля или числа резервных копий АРМ. ТРЕБУЕТСЯ паспорт конкретного развертывания и испытание на объявленной нагрузке. Резервная копия того же узла не умножает число независимых подчиненных."
        },
        {
          "type": "table",
          "headers": [
            "Измерение",
            "Что фиксируется"
          ],
          "rows": [
            [
              "Топология",
              "Число узлов, приборов и ресурсов, глубина маршрута, число целей группы и пересечения путей."
            ],
            [
              "Канал",
              "Полоса, RTT, потери, восстановление соединений и допустимые перерывы."
            ],
            [
              "Конкурентность",
              "Число одновременно запускаемых приборов, наблюдателей, опросов и сервисных работ; ограниченный пул на АРМ."
            ],
            [
              "Ограничение нагрузки",
              "Квоты очередей, событий, отчетов, discovery и reconnect; разброс задержек повторного подключения."
            ],
            [
              "Время",
              "Срок приема, подготовка, время до наблюдаемого действия, session.stop, таймауты источников; критический путь вместо безусловного умножения таймеров."
            ],
            [
              "Отказы",
              "Шторм событий, недоступные ветви, разделение сети, потеря питания и насыщение журнала; отдельно каждый ожидаемый результат."
            ]
          ]
        },
        {
          "type": "p",
          "text": "АРМ распараллеливает работу разных приборов в пределах квот, а УУ сохраняет установленный порядок изменяющих команд и воздействий на общие ресурсы ОУ. Оповещение не ожидает полного последовательного обхода всей диагностики. Численные целевые показатели задаются требованиями развертывания и подтверждаются стендовыми измерениями."
        },
        {
          "type": "p",
          "text": "Предел задержки session.stop измеряется с учетом уже выполняемой операции внутреннего интерфейса, защищенной записи TLS, медленного читателя TCP, Flash и криптографической обработки. Приоритет внутри очереди не прерывает уже заблокированный обмен. Нужны конечные сроки каждого шага, ограничение исходящих данных и сохраняемая возможность принять отмену. Конкретные пределы подтверждаются на целевой плате."
        }
      ]
    },
    {
      "id": "program-stages",
      "title": "24. Этапы программы и повторы",
      "blocks": [
        {
          "type": "p",
          "text": "Сценарий АРМ, запуск runId, ветвь branchId, сеанс УУ и этап программы различаются. Сирена, пауза, встроенная запись и живая речь не объединяются одним неопределенным признаком \"идет оповещение\". Изменение этапа сохраняет происхождение запуска и права остановки."
        },
        {
          "type": "table",
          "headers": [
            "Правило",
            "Контракт"
          ],
          "rows": [
            [
              "Контекст этапа",
              "revision программы, stageIndex и passIndex; результаты и отложенные действия относятся к точному этапу. Поздний ответ прежнего этапа не завершает новый."
            ],
            [
              "Переход",
              "Профиль определяет порядок остановки, подтверждения состояния, переключения входа и подготовки следующего действия. Нельзя включить речь и затем выполнить задержанный session.stop сирены."
            ],
            [
              "Повтор записи",
              "Различаются повтор доставки команды и новый проход программы. Для нового прохода нужны подтвержденный конец предыдущего, предел числа проходов и длительности, интервал паузы и собственный контекст."
            ],
            [
              "Отмена",
              "Запрещает еще не исполненные этапы, повторы, таймеры и выдачу новых медиаразрешений. Подтверждение отмены не теряется после очистки исполнительной очереди."
            ],
            [
              "Завершение",
              "Конец шага, выключение ресурса и завершение всей ветви являются отдельными результатами. Таймер АРМ не заменяет наблюдение результата ОУ."
            ]
          ]
        },
        {
          "type": "heading",
          "id": "program-stages-boundaries",
          "text": "Границы автономности"
        },
        {
          "type": "p",
          "text": "АРМ связывает отдельные конечные сеансы в управляемый им план либо запускает единый автономный mode=program. Для первого варианта следующий session.start является отдельным решением АРМ и требует живой связи; для второго весь план принят УУ заранее и исполняется по правилам настоящего раздела. Выбор варианта входит в задание оповещения."
        },
        {
          "type": "p",
          "text": "Завершение сирены в плане \"сирена, затем речь\" закрывает этап сирены, а не всю ветвь. Для подготовки следующего этапа АРМ различает ожидание, готовность, неподдержанную функцию, отказ и неизвестный результат; показывает причину и число ожидаемых получателей. Недоступность медиарежима не маскируется под продолжающуюся подготовку или сетевой таймаут. Сводная готовность группы не доказывает готовность каждого прибора: учитываются возможности конкретных получателей и заранее выбранная политика частичного исполнения."
        },
        {
          "type": "p",
          "text": "Отображение ожидания и доступность кнопок относятся к интерфейсу АРМ. Они не добавляют XML-команд и не заменяют проверку готовности и прав исполнителем при фактическом запуске. Полученное прежде ready не дает права обойти изменившееся состояние УУ."
        },
        {
          "type": "note",
          "text": "Для сценария, требующего завершения последовательности \"сирена, затем речь\" после потери АРМ, НЕОБХОДИМО использовать purpose=real, mode=program с локальной записью и onControlLoss=finish. Последовательность самостоятельных сетевых session.start не удовлетворяет этому требованию."
        },
        {
          "type": "p",
          "text": "Ядро определяет один конечный сеанс siren, recording, program, stream или test. Объявление режима требует исполнения всего его контракта. Владение обычного оповещения сохраняется между самостоятельными сеансами при действующем lease; технический тест освобождает эпоху автоматически после off и закрытия результата. Автономный plan фиксируется единым mode=program и выполняется без новых сетевых session.start."
        },
        {
          "type": "heading",
          "id": "program-stages-normative",
          "text": "Автономное исполнение составной программы"
        },
        {
          "type": "p",
          "text": "Составное оповещение с автономным завершением запускается одним session.start с mode=program и дочерним program, содержащим id и revision. Программа хранится на стороне УУ как неизменяемый манифест из 1..8 последовательных этапов siren, recording или pause. Ревизия идентифицирует весь упорядоченный план, параметры и манифесты записей. session.start фиксирует полный план в журнале намерения до первого воздействия; будущие этапы не требуют новых сетевых session.start."
        },
        {
          "type": "table",
          "headers": [
            "Поле программы",
            "Правило"
          ],
          "rows": [
            [
              "id / revision",
              "id: 1..32 ASCII-символа; revision: uint64. Изменение любого этапа или содержимого создает новую ревизию. Программа и ее содержимое недоступны для изменения или удаления до завершения всех ссылающихся исполнений."
            ],
            [
              "stage",
              "index: 1..8 без пропусков; kind=siren|recording|pause; repeat: 1..16; gapMs: 0..60000. Ресурсы наследуются из session.start; скрытые дополнительные ресурсы запрещены."
            ],
            [
              "siren",
              "pattern=continuous|intermittent, stopMode=duration в XML programs.list, durationMs=100..3600000, signalProfileId и signalProfileRevision. Параметры каждого прохода неизменны."
            ],
            [
              "recording",
              "contentId, revision, hashAlgorithm=streebog256, hash из 64 строчных hex; целиком локально доступная запись, один проход за повтор."
            ],
            [
              "pause",
              "durationMs=0..60000, repeat=1, gapMs=0. Выходы остаются off; отмена в паузе запрещает следующий этап."
            ],
            [
              "Предел",
              "Сумма всех подготовок, проходов, остановок и пауз ДОЛЖНА укладываться в maxDurationMs session.start <= 86400000. УУ проверяет верхнюю оценку до accepted. При неизвестной верхней длительности записи session.start отклоняется."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Все ресурсы и записи программы проверяются до accepted. Для перехода к следующему этапу УУ подтверждает off предыдущего, прекращает его таймеры и медиаразрешения и присваивает локальные stageIndex/passIndex. Позднее сообщение предыдущего этапа не действует на новый. Любой отказ или unknown прерывает оставшуюся программу и инициирует остановку; пропуск этапа при ошибке запрещен. succeeded относится ко всей программе и требует выполнения всех этапов."
        },
        {
          "type": "p",
          "text": "Результат program дополнительно содержит stageResult для каждого предусмотренного прохода: stageIndex, passIndex, state=pending|running|stopping|finished|notStarted и, при finished, result/reason. Поля runningAtMs, stoppingAtMs, finishedAtMs присутствуют только для состоявшихся переходов. acceptedAtMs <= runningAtMs <= stoppingAtMs <= finishedAtMs <= evaluatedAtMs; время следующего runningAtMs не меньше предыдущего finishedAtMs + gapMs между повторами. Времена каждого прохода лежат между началом плана и его finishedAtMs, если он завершен. Для pause runningAtMs обозначает начало паузы и не является доказательством включения. pending относится только к будущему проходу активного плана; после прекращения плана он становится notStarted с reason=programAborted из закрытого словаря, без result и времен воздействия; programAborted запрещен у session и у finished-этапа. Последний исполненный проход задает начало общей terminal stopping; промежуточные остановки этапов не меняют общую timeline. У каждого прохода применяются собственные prepareTimeoutMs, stopBeginMs и stopConfirmMs аппаратного профиля. Пара stageIndex/passIndex уникальна и соответствует зафиксированному манифесту. Снимок содержит все проходы, включая notStarted; до session.start УУ проверяет, что их максимальное число укладывается в объявленные память, snapshotMaxPages и snapshotReadMaxMs. Успех требует finished/succeeded всех проходов."
        },
        {
          "type": "p",
          "text": "При purpose=real onControlLoss=finish сохраняет исполнение всего принятого плана, включая подготовку, паузы и последующие локальные речевые этапы, в исходном maxDurationMs. stop отменяет все еще не исполненные этапы. После reboot автоматическое возобновление программы запрещено: выполняется восстановление ресурсов. Живой микрофон не включается в автономный план; для заранее предусмотренного сообщения используется локальная запись."
        },
        {
          "type": "p",
          "text": "programs.list является обязательным чтением при объявлении mode=program. Запрос содержит selection с необязательным programId либо парой snapshotId/cursor. Ответ programs содержит snapshotId, capabilityRevision, more и cursor при more=true. Записи program несут id/revision/stageCount; записи stage несут programId/index/kind/repeat/gapMs и один дочерний siren, recording или pause с полями таблицы. Полный снимок содержит ровно stageCount различных индексов каждого выбранного program. gapMs применяется между повторами одного этапа, после последнего повтора дополнительная пауза отсутствует. Манифесты передаются страницами в пределах общих лимитов XML; снимок не запускает исполнение."
        }
      ]
    },
    {
      "id": "local-control",
      "title": "25. Внешние входы и автономное управление",
      "blocks": [
        {
          "type": "p",
          "text": "Внешний контакт, местная кнопка, тангента и запуск из другой системы являются отдельными источниками. Наличие входа не дает ему права прерывать сетевой сеанс. Каталог указывает реально поддержанный источник, а конфигурация - разрешенное действие, область ресурсов и правила взаимодействия с действующим управлением."
        },
        {
          "type": "table",
          "headers": [
            "Случай",
            "Требуемое поведение"
          ],
          "rows": [
            [
              "Новое срабатывание",
              "Источник и эпизод однозначно идентифицированы; правило запуска имеет ID и ревизию. Решение, запуск и его происхождение связаны в журнале."
            ],
            [
              "Дребезг и удержание",
              "Профиль задает обработку фронта/уровня, подавление дребезга, повторное взведение и максимальную длительность. Частота событий не становится частотой повторного запуска."
            ],
            [
              "Снимок и повтор события",
              "Начальная синхронизация, восстановление истории и повтор доставки не запускают сценарий заново. Один эпизод не создает несколько независимых запусков."
            ],
            [
              "Уже активный вход после питания",
              "Отличается от нового фронта. Запрет автоматического возобновления прежнего сетевого сеанса сохраняется; trigger=edge требует нового фронта после повторного взведения; trigger=level повторно оценивает текущее физическое условие после восстановления готовности."
            ],
            [
              "ОУ запущено вне УУ",
              "Передается наблюдаемое внешнее управление и его доступный источник. Чужое исполнение не присваивается текущему sessionId, и ложный успех нашей команды не формируется."
            ],
            [
              "Отмена и аппаратная защита",
              "Сетевое прерывание проверяет происхождение. Для местного запуска и защитного отключения профиль отдельно задает полномочия и реакцию; универсальный удаленный reset не вводится."
            ]
          ]
        },
        {
          "type": "p",
          "text": "АРМ исполняет централизованные правила, а УУ - локальные правила из зафиксированной конфигурации. Закрытый формат localRules задает идентичность, редакцию, привязку входа, активную полярность, trigger, фильтры, программу, ресурсы и полный срок. Правила edge и level различаются по условиям создания нового эпизода, описанным ниже. Удержанный контакт и восстановление снимка не являются фронтом. Местный запуск получает отдельные principalId/controlEpoch/sessionId; сетевой клиент не присваивает это владение. Конфликт не прерывает действующий сеанс. Для edge он дает отказ без отложенного включения; для level условие повторно проверяется после освобождения ресурса."
        },
        {
          "type": "p",
          "text": "Локальный источник имеет зарегистрированную идентичность и собственный сеанс. Свободный ресурс может запускаться только разрешенным правилом входа. При сетевом владении несовместимое местное включение отклоняется и регистрируется; при местном исполнении сетевой session.start получает busy. Аппаратная аварийная защита действует независимо по паспорту ОУ. Если изделие допускает физическое ручное включение в обход УУ, оно ДОЛЖНО сообщать externalControl и не подтверждать его как исполнение сетевого session.start."
        },
        {
          "type": "heading",
          "id": "local-control-trigger-semantics",
          "text": "Импульс и длительно действующее условие тревоги"
        },
        {
          "type": "p",
          "text": "Локальные правила задаются коллекцией localRules схемы open-alert-config/5. Для trigger=edge при загрузке, применении нового правила и после каждого эпизода вход взводится только после неактивного значения в течение rearmMs. Затем активное значение в течение debounceMs создает один новый эпизод. Активный уровень при загрузке, удержание контакта и повтор сетевого события не являются новым фронтом. Если ресурс занят при фронте, отказ регистрируется без отложенного исполнения; повторное взведение требует нового неактивного интервала."
        },
        {
          "type": "p",
          "text": "trigger=level означает действующее физическое условие. После загрузки или атомарного применения правила УУ сначала завершает восстановление ресурсов, подтверждает off и готовность, затем получает новое valid/fresh наблюдение своего входа. Устойчивое активное значение в течение debounceMs разрешает новый локальный запуск даже без нового фронта. Это решение по текущему входу с новым bootId/sessionId, а не повтор прежнего сетевого session.start или восстановление старой программы. УУ фиксирует источник, правило, configRevision и наблюдение, обусловившее решение. Старый снимок, неизвестное значение и неисправный вход запуск не разрешают."
        },
        {
          "type": "p",
          "text": "Для level при занятости ресурса активное условие сохраняется как ожидающее наблюдение, без заранее принятого session.start. После освобождения выполняются заново проверка прав, готовности, свежести и debounceMs. Устойчивое неактивное значение rearmMs снимает ожидание. Для одной редакции правила в одной непрерывной загрузке один активный эпизод создает не более одного принятого исполнения, включая случай его остановки или ошибки; повтор разрешен после неактивности rearmMs. После reboot применяется новое наблюдение по предыдущему правилу. Само снятие уровня не отменяет уже принятую конечную программу; ее остановка подчиняется полномочиям, maxDurationMs и аппаратной защите. Любой локальный запуск получает собственное владение и не прерывает чужой сеанс."
        }
      ]
    }
  ],
  "commands": [
    {
      "id": "cmd-transfer-capabilities",
      "name": "transfer.capabilities",
      "title": "Прочитать параметры файлового канала",
      "group": "Обслуживание",
      "access": "Аутентифицированный участник с правом чтения возможностей; владение НЕ ТРЕБУЕТСЯ.",
      "input": "Без дочерних элементов; управляющее соединение.",
      "output": "transfer profile/port/maxBlockBytes/maxWindowBlocks/maxInFlightBytes.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported",
      "behavior": "Не изменяет данные и не продлевает lease. Параметры и допустимые сочетания определены в разделе 14.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно при любой сетевой файловой функции"
    },
    {
      "id": "cmd-transfer-hello",
      "name": "transfer.hello",
      "title": "Привязать файловое соединение",
      "group": "Обслуживание",
      "access": "Тот же principalId, живое управляющее соединение и maintenance-эпоха владельца транзакции.",
      "input": "Первый XML отдельного соединения того же режима tls либо plain: bootId/controlEpoch, transfer id/kind; cmdSeq отсутствует.",
      "output": "ready id/blockBytes/windowBlocks/receivedBytes/remainingBudgetMs, затем двоичные кадры bulk/1.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, resultExpired, unsafeState, notFound, staleAttachment",
      "behavior": "Атомарная привязка к экземплярам двух соединений. Неуспешное подключение не изменяет существующую передачу; полные правила раздела 14.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно при любой сетевой файловой функции"
    },
    {
      "name": "firmware.cancel",
      "title": "Отменить прием или проверку образа",
      "group": "Обслуживание",
      "input": "upload id; cmdSeq владельца maintenance.",
      "output": "upload id/state=cancelled; после activating - conflict.",
      "obligation": "Обязательно при обновлении прошивки",
      "id": "cmd-firmware-cancel",
      "maturity": "open-alert-draft/0.20",
      "access": "Владелец maintenance с административным правом соответствующей операции.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, storageFault",
      "behavior": "Отмена использует общие проверки контекста и дедупликацию cmdSeq, отзывает привязку передачи и останавливает незавершенную проверку. После атомарного commit возвращает conflict/detail=operationStateMismatch. Точный повтор возвращает сохраненный ответ без повторного действия."
    },
    {
      "name": "profiles.get",
      "title": "Паспорт физического исполнения",
      "group": "Наблюдение",
      "input": "selection resourceId либо resourceId/snapshotId/cursor.",
      "output": "profiles с profile и parameter по разделу 8.",
      "obligation": "Обязательно: ядро",
      "id": "cmd-profiles-get",
      "maturity": "open-alert-draft/0.20",
      "access": "Право чтения выбранной области.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, notFound, unsupported, snapshotExpired",
      "behavior": "Чтение не изменяет выходы, операцию или lease. Новый запрос читает актуальное состояние; продолжение снимка сохраняет его контекст."
    },
    {
      "name": "programs.list",
      "title": "Каталог автономных программ",
      "group": "Наблюдение",
      "input": "selection programId либо snapshotId/cursor.",
      "output": "Страницы programs с program и stage; формат раздела 24.",
      "obligation": "Обязательно при mode=program",
      "id": "cmd-programs-list",
      "maturity": "open-alert-draft/0.20",
      "access": "Право чтения выбранной области.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound, snapshotExpired",
      "behavior": "Чтение не изменяет выходы, операцию или lease. Новый запрос читает актуальное состояние; продолжение снимка сохраняет его контекст."
    },
    {
      "name": "audio.prepare",
      "title": "Подготовить прием потока",
      "group": "Речь",
      "input": "stream с параметрами RTP, resources, route и buffer по разделу 13; обычный cmdSeq владельца.",
      "output": "prepared mediaId/expiresAtMs/configRevision/capabilityRevision.",
      "obligation": "Обязательно при mode=stream",
      "id": "cmd-audio-prepare",
      "maturity": "open-alert-draft/0.20",
      "access": "Действующий владелец с правом выбранного воздействия.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, unsafeState",
      "behavior": "Изменение использует общие проверки контекста и дедупликацию cmdSeq. Точный повтор возвращает сохраненное решение без повторного действия и продления предметных сроков. Переходы операции и межполевые условия определены ее предметным контрактом."
    },
    {
      "name": "audio.state",
      "title": "Прочитать состояние потока",
      "group": "Речь",
      "input": "selection mediaId.",
      "output": "audio со состоянием и счетчиками по разделу 13.",
      "obligation": "Обязательно при mode=stream",
      "id": "cmd-audio-state",
      "maturity": "open-alert-draft/0.20",
      "access": "Право чтения выбранной области.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound",
      "behavior": "Чтение не изменяет выходы, операцию или lease. Новый запрос читает актуальное состояние; продолжение снимка сохраняет его контекст."
    },
    {
      "name": "audio.finish",
      "title": "Зафиксировать конец источника",
      "group": "Речь",
      "input": "stream mediaId/lastPacketIndex/sampleCount; обычный cmdSeq владельца.",
      "output": "eof с теми же полями; полный физический итог через session.get.",
      "obligation": "Обязательно при mode=stream",
      "id": "cmd-audio-finish",
      "maturity": "open-alert-draft/0.20",
      "access": "Действующий владелец с правом выбранного воздействия.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, unsafeState",
      "behavior": "Изменение использует общие проверки контекста и дедупликацию cmdSeq. Точный повтор возвращает сохраненное решение без повторного действия и продления предметных сроков. Переходы операции и межполевые условия определены ее предметным контрактом."
    },
    {
      "name": "audio.discard",
      "title": "Освободить неиспользованную подготовку",
      "group": "Речь",
      "input": "prepared mediaId; обычный cmdSeq владельца.",
      "output": "discarded mediaId; для активного сеанса unsafeState.",
      "obligation": "Обязательно при mode=stream",
      "id": "cmd-audio-discard",
      "maturity": "open-alert-draft/0.20",
      "access": "Действующий владелец с правом выбранного воздействия.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, unsafeState",
      "behavior": "Изменение использует общие проверки контекста и дедупликацию cmdSeq. Точный повтор возвращает сохраненное решение без повторного действия и продления предметных сроков. Переходы операции и межполевые условия определены ее предметным контрактом."
    },
    {
      "name": "content.begin",
      "title": "Начать размещение записи",
      "group": "Обслуживание",
      "input": "content с неизменным манифестом и transfer с параметрами bulk/1; обычный cmdSeq.",
      "output": "upload id/receivedBytes/remainingBudgetMs.",
      "obligation": "Обязательно при сетевой загрузке контента",
      "id": "cmd-content-begin",
      "maturity": "open-alert-draft/0.20",
      "access": "Владелец maintenance с административным правом соответствующей операции.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, unsafeState, noSpace, storageFault",
      "behavior": "Изменение использует общие проверки контекста и дедупликацию cmdSeq. Точный повтор возвращает сохраненное решение без повторного действия и продления предметных сроков. Переходы операции и межполевые условия определены ее предметным контрактом."
    },
    {
      "name": "content.commit",
      "title": "Проверить и опубликовать запись",
      "group": "Обслуживание",
      "input": "upload id; обычный cmdSeq; только после полного приема.",
      "output": "content id/revision/state=committed либо accepted с operation.id=upload.id; итог в content.status.",
      "obligation": "Обязательно при сетевой загрузке контента",
      "id": "cmd-content-commit",
      "maturity": "open-alert-draft/0.20",
      "access": "Владелец maintenance с административным правом соответствующей операции.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, unsafeState, storageFault",
      "behavior": "Изменение использует общие проверки контекста и дедупликацию cmdSeq. Точный повтор возвращает сохраненное решение без повторного действия и продления предметных сроков. Переходы операции и межполевые условия определены ее предметным контрактом."
    },
    {
      "name": "content.status",
      "title": "Состояние размещения записи",
      "group": "Обслуживание",
      "input": "selection uploadId.",
      "output": "upload id/state/receivedBytes/totalBytes/remainingBudgetMs; progressBytes в verifying, reason в failed; раздел 14.",
      "obligation": "Обязательно при сетевой загрузке контента",
      "id": "cmd-content-status",
      "maturity": "open-alert-draft/0.20",
      "access": "Право чтения выбранной области.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound, storageFault",
      "behavior": "Чтение не изменяет выходы, операцию или lease. Новый запрос читает актуальное состояние; продолжение снимка сохраняет его контекст."
    },
    {
      "name": "content.cancel",
      "title": "Отменить размещение записи",
      "group": "Обслуживание",
      "input": "upload id; обычный cmdSeq.",
      "output": "upload id/state=cancelled.",
      "obligation": "Обязательно при сетевой загрузке контента",
      "id": "cmd-content-cancel",
      "maturity": "open-alert-draft/0.20",
      "access": "Владелец maintenance с административным правом соответствующей операции.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, storageFault",
      "behavior": "Отмена использует общие проверки контекста и дедупликацию cmdSeq, отзывает привязку передачи и останавливает незавершенную проверку. После атомарного commit возвращает conflict/detail=operationStateMismatch. Точный повтор возвращает сохраненный ответ без повторного действия."
    },
    {
      "id": "cmd-media-hello",
      "name": "media.hello",
      "title": "Привязать медиасоединение",
      "group": "Речь",
      "access": "Тот же аутентифицированный principalId, что создал живую подготовку; действующая эпоха и прикрепленный управляющий канал.",
      "input": "Первый кадр отдельного медиасоединения выбранного режима tls|tcp: request v/profile/id/action/bootId/controlEpoch и единственный stream mediaId (32 hex). cmdSeq отсутствует; protocol.hello не передается.",
      "output": "response v/profile/to/bootId/status=ok и единственный ready mediaId; после полной отправки ответа следуют бинарные рамки uint16 BE + RTP либо служебные рамки часов по rtp-alert/4.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, notFound, resultExpired",
      "behavior": "Проверки: оболочка и выбранный профиль; роль/область/principalId; bootId/эпоха/управляющая привязка; существование и срок mediaId; отсутствие другой медиапривязки. Чужая подготовка дает forbidden/detail=ownerMismatch до раскрытия ее состояния, занятая привязка busy/detail=capacityExhausted. Неизвестный mediaId дает notFound, известный истекший resultExpired. Отказ имеет обычный status=error/error code, detail только для forbidden/busy, затем закрывается только новое соединение. Повтор hello после успешного ответа на том же канале запрещен: XML уже не является кадром RTP. Повтор подключения не вытесняет действующий медиаканал. Потерянный ответ выясняется через audio.state; повтор подключения допустим только до принятого session.start и после освобождения прежней привязки в пределах срока подготовки. Для уже принятого session.start потеря связанного канала завершает сеанс; новый канал с прежним mediaId запрещен. Ни cmdSeq, ни lease, ни срок подготовки не изменяются. Поздний обработчик закрытого соединения не затрагивает новую привязку.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно при mode=stream"
    },
    {
      "id": "cmd-audio-group-prepare",
      "name": "audio.group.prepare",
      "title": "Подготовить групповую речь",
      "group": "Речь",
      "access": "Владелец с правом на все ресурсы шаблона mediaGroups; sourcePrincipalId совпадает с участником.",
      "input": "group id/revision/streamId/source; totalSamples только для recording; key (64 hex) только при tls. Обычный cmdSeq. Полный контракт group-audio/1 и group-audio-open/1 — нормативное приложение Ж; в онлайн-версии — раздел «Групповая передача речи».",
      "output": "ok, prepared mediaId/expiresAtMs/configRevision/capabilityRevision. Выходы не включаются; session.start выполняется отдельно.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, unsafeState, notFound, sourceUnavailable",
      "behavior": "Точный повтор возвращает прежний mediaId без продления 60000 мс. Измененное содержимое повтора — conflict. Отсутствие шаблона — notFound, его другая редакция — conflict, неподтвержденные часы — sourceUnavailable. Чужой sourcePrincipalId — forbidden/detail=ownerMismatch. После session.stop/reboot разрешение и оперативный ключ отозваны. Подготовка не обходит неподтвержденный off и не вытесняет активный поток.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно при объявлении group-audio/1 либо group-audio-open/1"
    },
    {
      "id": "cmd-protocol-hello",
      "name": "protocol.hello",
      "title": "Выбрать профиль обмена",
      "group": "Управление",
      "access": "Участник завершил TLS-аутентификацию либо явно выбрал разрешенный plain; предметный профиль еще не выбран.",
      "input": "v=1, profile=bootstrap/1, id, action; hello содержит 1-8 profile id; при plain обязателен атрибут clientId (32 hex), при tls запрещен. Порядок списка не задает приоритет УУ.",
      "output": "ok, bootId и hello selectedProfile/deviceId/security=tls|plain. Выбран разрешенный общий профиль по приоритету УУ, независимо от порядка предложения клиента.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden",
      "behavior": "Точный повтор до первой предметной команды возвращает тот же выбор. После фиксации профиля другой hello запрещен; изменение предложения требует нового соединения.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-readiness-get",
      "name": "readiness.get",
      "title": "Оценить готовность конкретного плана",
      "group": "Наблюдение",
      "access": "Право наблюдения за выбранными ресурсами; владение оповещением НЕ ТРЕБУЕТСЯ.",
      "input": "plan mode/configRevision/capabilityRevision/maxDurationMs, resources и элемент режима по разделу 20; продолжение selection snapshotId/cursor.",
      "output": "readiness snapshotId/evaluatedAtMs/validUntilMs/stateRevision/more/cursor, summary, resource и evidence по разделу 20.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Чтение не включает ОУ, не запускает тест или полное хеширование, не резервирует ресурс и не поддерживает связь владельца. session.start повторно проверяет актуальные условия; старое ready не разрешает обход препятствий.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-content-describe",
      "name": "content.describe",
      "title": "Прочитать манифест и доказательства проверки записи",
      "group": "Наблюдение",
      "access": "Право чтения описаний контента в заданной области.",
      "input": "selection contentId/revision либо snapshotId/cursor.",
      "output": "contents с записями content и verification по разделу 20.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound, snapshotExpired, storageFault",
      "behavior": "Чтение не изменяет контент. verified ДОПУСКАЕТСЯ только при совпадении хеша доступных байтов с неизменным манифестом; unknown не содержит actualHash. session.start требует verified.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно при mode=recording или program"
    },
    {
      "id": "cmd-device-describe",
      "name": "device.describe",
      "title": "Идентификация изделия",
      "group": "Наблюдение",
      "access": "Аутентифицированный участник с правом чтения; владение НЕ ТРЕБУЕТСЯ.",
      "input": "Без предметных параметров. Общие v, profile и id обязательны; запрос выполняется после аутентификации.",
      "output": "bootId в оболочке; device deviceId, kind=vau|siren, version, uptimeMs, securityPolicy=optional|secureOnly; отдельный info protocolName=Открытый протокол систем оповещения; manufacturer/softwareDeveloper необязательны и указывают фактических изготовителей. Профиль уже выбран через bootstrap.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Не возвращает ключи, пароли и заводские секреты.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-device-capabilities",
      "name": "device.capabilities",
      "title": "Возможности и пределы",
      "group": "Наблюдение",
      "access": "Читатель.",
      "input": "Без дочернего элемента для первой страницы; selection snapshotId/cursor для продолжения каталога возможностей УУ.",
      "output": "capabilityRevision, драйверы и привязки ОУ, поддержанные команды, режимы, ресурсные группы конфликтов, глубина доказательств, лимиты сообщений и опросов, виды собственных запросов УУ.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, snapshotExpired, notFound",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Функция считается доступной только при явном объявлении для нужного ресурса.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-device-state",
      "name": "device.state",
      "title": "Согласованное текущее состояние",
      "group": "Наблюдение",
      "access": "Читатель.",
      "input": "Без дочернего элемента для нового снимка; selection snapshotId/cursor для продолжения.",
      "output": "bootId, stateRevision, eventSeq; обязательные configuration с configRevision/appliedRevision/onControlLoss, ownership/recovery; resourceCount/alarmCount/sessionCount и страницы ресурсов, текущего сеанса, активных и неизвестных аварий.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Отсутствие активного сеанса не доказывает успешного окончания предыдущего.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-inventory-get",
      "name": "inventory.get",
      "title": "Каталог оборудования и точек",
      "group": "Наблюдение",
      "access": "Читатель.",
      "input": "Выборка по pointId, resourceId или ouId; cursor/snapshotId для продолжения.",
      "output": "Страницы самостоятельных записей point с id и facet=identity|meaning|source|acquisition; enum pointId/code/label и threshold pointId/conditionId/operator/value/hysteresis/severity отдельными записями. Иерархия, типы, единицы, presence, support, readMethod, источник; capabilityRevision и bindingGeneration в контексте снимка.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, notFound, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Отсутствующая точка и неподдерживаемая точка различаются.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-sensors-read",
      "name": "sensors.read",
      "title": "Показания без воздействия",
      "group": "Наблюдение",
      "access": "Читатель.",
      "input": "selection с 1-8 point id, без повторов. Продолжение содержит тот же набор и пару snapshotId/cursor.",
      "output": "Значения вместе с validity, freshness, reason, sourceId, sampleSeq и временем наблюдения; метаданные снимка.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, notFound, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Кеш не освежается фактом чтения; физический тест не запускается.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-diagnostics-read",
      "name": "diagnostics.read",
      "title": "Инженерная диагностика",
      "group": "Наблюдение",
      "access": "Право диагностики; обычное чтение может иметь меньшую детализацию.",
      "input": "selection scope=controller|ethernet|control|ouInterface|media либо snapshotId/cursor.",
      "output": "diagnostics snapshotId/uptimeMs/more/cursor и metric name/type/value/unit по разделу 11.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Не сбрасывает счетчики, не перезагружает интерфейс и не меняет выходы.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-tests-list",
      "name": "tests.list",
      "title": "Профили опросов и функциональных проверок",
      "group": "Наблюдение",
      "access": "Читатель.",
      "input": "selection с необязательным testId либо snapshotId/cursor.",
      "output": "tests snapshotId/capabilityRevision/more/cursor; test, check и observation по разделу 10.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Перечень не запускает проверку. Выполнение: session.start с mode=test. Формат tests.list определен в разделе 10; каталог содержит все критерии и наблюдения принятого плана.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно при mode=test"
    },
    {
      "id": "cmd-log-read",
      "name": "log.read",
      "title": "Журнал устройства",
      "group": "Наблюдение",
      "access": "Право чтения журнала.",
      "input": "selection afterSeq/limit, limit=1..8.",
      "output": "log oldestSeq/nextSeq/gap/more и entry seq/atMs/level/code/sessionId по разделу 11.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, cursorExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Ключи и секреты отсутствуют; утрата истории не заменяется пустым успешным результатом.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-poll-start",
      "name": "poll.start",
      "title": "Опрос без запуска ОУ",
      "group": "Опросы",
      "access": "Право опроса; владение оповещением НЕ ТРЕБУЕТСЯ.",
      "input": "poll clientOperationId/deadlineMs/refresh=cache|readBus, 1..8 point id.",
      "output": "accepted, operation id/clientOperationId/state=queued; результат через poll.get.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, conflict",
      "behavior": "clientOperationId дедуплицируется в principalId/bootId до deadlineMs+resultRetentionMs. Другой набор полей с тем же ID дает conflict. Квоты: одно задание на участника, два на УУ. Чтение не включает выходы и не продлевает lease.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-poll-get",
      "name": "poll.get",
      "title": "Результаты опроса",
      "group": "Опросы",
      "access": "Создатель задания или роль с разрешенным доступом к нему.",
      "input": "selection operationId либо operationId/snapshotId/cursor.",
      "output": "poll snapshotId/operationId/state=queued|running|finished|cancelled/expectedCount/complete/evaluatedAtMs/more/cursor; point по формату sensors.read.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. complete означает собранный набор результатов, а не исправность всех точек.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-poll-cancel",
      "name": "poll.cancel",
      "title": "Отменить свой опрос",
      "group": "Опросы",
      "access": "Создатель задания с правом опроса.",
      "input": "poll operationId/clientOperationId.",
      "output": "cancelled operationId; уже полученные результаты сохраняются.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound",
      "behavior": "Не отменяет чужие опросы и не останавливает оповещение. Повтор для доступного завершенного задания возвращает его состояние; уже начатое получение наблюдений от ОУ может закончиться до отмены.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-control-acquire",
      "name": "control.acquire",
      "title": "Получить управление",
      "group": "Управление",
      "access": "Участник с правом управления; активного несовместимого владельца нет.",
      "input": "control acquireSeq/instanceId/purpose=real|exercise|technical|maintenance; контекст bootId. Назначение проверяется по правам и сохраняется в эпохе.",
      "output": "control с acquireSeq, instanceId, purpose, controlEpoch, highWater, attachSeq, leaseRemainingMs, current и idleDeadlineMs при отсутствии исполнения. Повторы и свободное владение - раздел 3.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, conflict, resultExpired, unsafeState, storageFault",
      "behavior": "Последний acquireSeq и решение хранятся для допущенного principalId до смены bootId. Точный повтор возвращает ту же эпоху; current=false означает завершенное владение. На новом канале повтор только возвращает решение, а прикрепление выполняет resume. Новый номер не захватывает чужой сеанс.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-control-resume",
      "name": "control.resume",
      "title": "Восстановить свое управление",
      "group": "Управление",
      "access": "Тот же логический владелец с подтвержденным правом восстановления.",
      "input": "bootId/controlEpoch в оболочке; control acquireSeq, instanceId и возрастающий attachSeq. Право резервной копии требует ограждения прежней по разделу 3.",
      "output": "control с прежними purpose/controlEpoch, новым attachSeq и текущими highWater/leaseRemainingMs/current; idleDeadlineMs не продлевается.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, staleEpoch, staleAttachment, conflict, storageFault",
      "behavior": "Живая прежняя привязка не вытесняется: busy/detail=ownershipHeld. После ее отсоединения старый канал лишен изменяющих полномочий. Смена IP допустима после проверки идентичности и прежнего контекста. Повторное подключение не сбрасывает номера, не перезапускает сеанс и не решает HA само по себе. При утрате контекста после перезапуска АРМ читает актуальный снимок и сохраненный результат; присвоение чужого исполнения запрещено.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-control-release",
      "name": "control.release",
      "title": "Освободить управление",
      "group": "Управление",
      "access": "Текущий владелец, нет активного сеанса или занятых неизвестным исходом ресурсов.",
      "input": "Текущие bootId/controlEpoch и новый cmdSeq.",
      "output": "ok и released controlEpoch/released=true. Точный повтор на исходном либо новом допущенном канале того же участника возвращает сохраненный ответ из кеша завершенной эпохи.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, staleEpoch, staleAttachment, conflict, resultExpired, unsafeState, storageFault",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. Не используется как замена явной остановке; при активном сеансе возвращает busy.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-channel-ping",
      "name": "channel.ping",
      "title": "Проверить жизнь канала",
      "group": "Управление",
      "access": "Аутентифицированный участник в своем канале.",
      "input": "id и bootId в оболочке; владелец передает также controlEpoch. Дочернего элемента и cmdSeq нет.",
      "output": "ok, bootId и channel uptimeMs/leaseRemainingMs. Для наблюдателя leaseRemainingMs=0.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, staleEpoch, staleAttachment",
      "behavior": "Отдельный слот heartbeat, период 5000 мс, lease 30000 мс. Только запрос прикрепленного владельца с текущей эпохой продлевает связь. Наблюдатели не продлевают владение.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-session-start",
      "name": "session.start",
      "title": "Начать оповещение, опрос с запуском или проверку",
      "group": "Сеансы",
      "access": "Текущий владелец; поддержанные ресурсы свободны и имеют допустимое физическое состояние.",
      "input": "bootId/controlEpoch/cmdSeq; session mode=siren|recording|test|program|stream, purpose, acceptBeforeMs, maxDurationMs, configRevision, capabilityRevision; resources с 1..4 resource id/bindingGeneration и ровно один элемент соответствующего режима. Форматы приведены в разделах 10, 13, 17 и 24.",
      "output": "accepted с session id/startSeq/state/onControlLoss/configRevision/capabilityRevision/acceptedAtMs/maxDurationMs. Принятие и физический результат раздельны; наблюдения и проверки читаются страницами session.get.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, staleEpoch, staleAttachment, conflict, resultExpired, unsupported, startWindowExpired, startBlockedByCancel, sourceUnavailable, unsafeState, notFound, storageFault",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. onControlLoss не переопределяется в session.start. Отмена без ответа: session.cancel по startSeq; с известным ID: session.stop. Все поля конкретного режима и быстрые обязательные предусловия проверяются до воздействия по текущим наблюдениям. Старое заключение readiness.get не резервирует ресурс и не заменяет повторной проверки.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-session-stop",
      "name": "session.stop",
      "title": "Остановить известный сеанс",
      "group": "Сеансы",
      "access": "Текущий владелец именно этого исполнения.",
      "input": "bootId/controlEpoch, новый cmdSeq и точный sessionId.",
      "output": "Принятие остановки/state=stopping; затем результат исполнения и отдельно подтвержденное состояние выходов.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, staleEpoch, staleAttachment, conflict, resultExpired, notFound, storageFault",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. Разделяет отдельный приоритетный слот с session.cancel и не ожидает ответа обычного чтения или сервисной команды. Старый sessionId никогда не означает \"остановить текущий\". Возврат accepted еще не доказывает физического выключения. Отмена удаляет будущие включения и повторы этого исполнения, отзывает его медиаразрешения; обработка session.stop НЕ ДОЛЖНА оставлять задержанный session.start в драйвере.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-session-cancel",
      "name": "session.cancel",
      "title": "Отменить неподтвержденный session.start",
      "group": "Сеансы",
      "access": "Текущий владелец соответствующего bootId/controlEpoch.",
      "input": "Новый cmdSeq и startSeq исходного session.start; 0 < startSeq < cmdSeq. sessionId знать НЕ ТРЕБУЕТСЯ.",
      "output": "Совпадающий активный сеанс переходит в stopping; иначе activeMatch=false, lateStartBlocked=true и честное состояние доступности истории.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, staleEpoch, staleAttachment, conflict, resultExpired, storageFault, startBlockedByCancel",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. Поднимает highWater и блокирует поздний запуск с указанным старым номером. Сохраненная отмена позволяет ответить startBlockedByCancel с cancelSeq, когда решение самого session.start отсутствует; после вытеснения истории остается resultExpired. Чужой или более новый сеанс не затрагивает. Решение по резервному номеру завершения не вытесняется до освобождения эпохи; условия допуска и нерасходования номера определены разделом 4.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-session-get",
      "name": "session.get",
      "title": "Узнать состояние и итог сеанса",
      "group": "Сеансы",
      "access": "Участник с правом чтения соответствующего исполнения.",
      "input": "bootId в оболочке; selection sessionId либо startSeq (для startSeq также controlEpoch в оболочке). Продолжение: те же поля выбора и пара snapshotId/cursor; одно поле без другого запрещено.",
      "output": "Неизменный result; одна session на первой странице, одна timeline и одна completion на весь снимок, resource/check/observation/execution/fault и для program все stageResult. Времена, полнота и result/reason определены разделами 6, 17 и 24.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, notFound, resultExpired, snapshotExpired",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. result содержит snapshotId, stateRevision, evaluatedAtMs, more и cursor только при more=true; сводка session есть только на первой странице; mode и счетчики resourceCount/checkCount/observationCount/executionCount/faultCount неизменны на всех страницах, далее целые timeline/resource/check/observation/execution/completion/fault/stageResult. Размер с оболочкой до 1024 байт. Конец страницы не является концом сеанса. Неизвестный исторический результат не выводится из текущего состояния устройства.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-command-get",
      "name": "command.get",
      "title": "Уточнить решение по команде",
      "group": "Сеансы",
      "access": "Владелец контекста либо явно разрешенная диагностическая роль.",
      "input": "selection controlEpoch/cmdSeq; bootId в оболочке.",
      "output": "command cmdSeq/state=accepted|rejected|expired; action и исходное тело решения при сохраненной истории по разделу 11.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, resultExpired, staleEpoch",
      "behavior": "Доступен только участнику исходной команды. Не исполняет ее повторно; expired не означает отсутствия воздействия. Сохраненное тело возвращается без исходных транспортных to/id.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-events-read",
      "name": "events.read",
      "title": "Получить события после курсора",
      "group": "Наблюдение",
      "access": "Читатель с правом полного журнала данного УУ; частичное право датчиков не дает доступа к базовому журналу.",
      "input": "Элемент events с bootId и afterSeq. Полей snapshotId/cursor и запрашиваемого размера страницы в ядре нет; размер ответа ограничивает УУ.",
      "output": "Контейнер events с nextSeq, more, gap и oldestSeq; внутри записи event с seq и stateRevision. more относится к текущей выборке. Возобновление связывается с границей eventSeq согласованного снимка.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy",
      "behavior": "Повтор с тем же afterSeq читает текущий доступный диапазон журнала; неизменность страницы не гарантируется. Доставка pull, без подписки и самопроизвольных сообщений. Продолжение задает afterSeq=nextSeq предыдущего обработанного ответа, без snapshotId/cursor. При gap=true записей нет, more=false, nextSeq=afterSeq; нужен новый согласованный снимок. Курсор сохраняется после проверки и обработки страницы; повторы seq не создают повторных событий, исторические события снимком не восстанавливаются.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Обязательно: ядро"
    },
    {
      "id": "cmd-config-get",
      "name": "config.get",
      "title": "Прочитать действующую конфигурацию",
      "group": "Обслуживание",
      "access": "Право чтения конфигурации.",
      "input": "selection configId либо transactionId или snapshotId/cursor.",
      "output": "configuration snapshotId/configRevision/appliedRevision/more/cursor; entry либо transaction по разделу 14.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, snapshotExpired, storageFault",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-config-stage",
      "name": "config.stage",
      "title": "Подготовить конфигурацию",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "transaction id/baseRevision/totalBytes/hashAlgorithm/hash и transfer profile/blockBytes/windowBlocks/windowRoundTripMaxMs; cmdSeq maintenance; open-alert-config/5.",
      "output": "transaction id/state/receivedBytes/remainingBudgetMs; новый прием receiving, повтор возвращает текущее состояние.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, noSpace, storageFault, unsafeState",
      "behavior": "Байты передаются отдельным каналом bulk/1. Полный прием запускает validating; config.get сообщает прогресс и staged/failed. Проверка ограничена 60000 мс, порции не более 100 мс; действующая конфигурация не меняется до commit.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-config-commit",
      "name": "config.commit",
      "title": "Применить подготовленные настройки",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "transaction id; обычный cmdSeq.",
      "output": "committed id/configRevision/appliedRevision; атомарное применение полного пакета.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, storageFault, unsafeState",
      "behavior": "Атомарно сохраняет и применяет только локальные правила получателя; configRevision=appliedRevision. Пакет не выполняет физическую перенастройку ОУ. Несовпадение baseRevision отклоняется до фиксации; повтор возвращает сохраненное решение.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-config-cancel",
      "name": "config.cancel",
      "title": "Отбросить подготовленные настройки",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "transaction id; обычный cmdSeq.",
      "output": "cancelled id; зафиксированная конфигурация сохраняется.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, storageFault",
      "behavior": "Повтор возвращает сохраненное решение; cancel после commit дает conflict.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-network-stage",
      "name": "network.stage",
      "title": "Подготовить смену сети",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "network id/address/prefix/gateway/confirmMs; обычный cmdSeq.",
      "output": "staged id/expiresAtMs; пробное применение с автоматическим возвратом по разделу 14.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, unsafeState, storageFault",
      "behavior": "Пробные параметры применяются после сохранения прежних; возврат по expiresAtMs или cancel. Повтор не продлевает срок. Невозможность надежного возврата отклоняет stage до изменения адреса.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-network-confirm",
      "name": "network.confirm",
      "title": "Подтвердить новый сетевой путь",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "network id; обычный cmdSeq; действующий канал того же principalId и режима tls либо plain через новый адрес до expiresAtMs.",
      "output": "network id/state=committed.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, confirmationWindowExpired, storageFault",
      "behavior": "Новый канал проходит protocol.hello и control.resume; после истечения эпохи требуется новое maintenance-владение того же участника. Режим фиксируется network.stage; смена канала или эпохи не продлевает expiresAtMs. Повтор в доступной истории возвращает решение. Опоздавший confirm не применяет параметры повторно.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-network-cancel",
      "name": "network.cancel",
      "title": "Отменить сетевой переход",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "network id; обычный cmdSeq.",
      "output": "network id/state=cancelled; восстановлены прежние параметры.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, notFound, storageFault",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. Если сеть потеряна, возврат обеспечивается таймером устройства, а не доставкой этой команды.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-time-set",
      "name": "time.set",
      "title": "Установить время",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "clock utcMs/errorMs; errorMs=0..500; обычный cmdSeq.",
      "output": "clock utcMs/errorMs; монотонные таймеры неизменны.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. Коррекция настенных часов не меняет монотонные таймеры сеанса и срок сборки сообщения.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-firmware-begin",
      "name": "firmware.begin",
      "title": "Начать прием образа",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "image id/deviceKind/hardwareRevision/version/bytes/hashAlgorithm/hash/signature/keyId и transfer profile/blockBytes/windowBlocks/windowRoundTripMaxMs; cmdSeq maintenance; upload.id=image.id.",
      "output": "upload id=image.id/state=receiving/receivedBytes/remainingBudgetMs. Повтор begin возвращает тот же ID и текущий префикс.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, incompatible, noSpace, unsafeState, storageFault",
      "behavior": "Не активирует образ. Байты передаются отдельным каналом bulk/1 того же явно выбранного режима tls либо plain с согласованными блоком и окном. Общий срок рассчитывается по числу окон; idleTimeoutMs=600000. Подтвержденный префикс и ID сохраняются при смене владения и reboot по разделу 14.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-firmware-verify",
      "name": "firmware.verify",
      "title": "Проверить принятый образ",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "upload id; обычный cmdSeq.",
      "output": "upload id/state=verified либо accepted с operation id=upload.id; firmware.status возвращает verifying/verified/failed; progressBytes присутствует только в verifying.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, incomplete, invalidSignature, incompatible, rollbackDenied, unsafeState, notFound, storageFault",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. Успешный хеш без проверки подлинности не разрешает установку.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-firmware-commit",
      "name": "firmware.commit",
      "title": "Активировать проверенный образ",
      "group": "Обслуживание",
      "access": "Действующий владелец с административным правом, подтвержденный off затрагиваемых ресурсов; раздел 14.",
      "input": "upload id; обычный cmdSeq; состояние verified и подтвержденный off.",
      "output": "accepted, operation id=upload.id. Допуск требует местного разрешения обслуживания и готового независимого тракта тревоги; итог через firmware.status.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, staleEpoch, staleAttachment, conflict, resultExpired, unsafeState, incompatible, incomplete, notFound, storageFault",
      "behavior": "Точный повтор cmdSeq в том же bootId/controlEpoch возвращает сохраненное решение без повторного действия. Если решение и сведения об адресной отмене вытеснены, старый номер получает resultExpired. Ответ до перезагрузки не доказывает успешную загрузку. Активация сохраняет независимый путь восстановления при потере питания по разделу 14.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    },
    {
      "id": "cmd-firmware-status",
      "name": "firmware.status",
      "title": "Уточнить состояние обновления",
      "group": "Обслуживание",
      "access": "Разрешенный участник операции обновления.",
      "input": "selection operationId; это тот же ID, что upload.id=image.id, с самого firmware.begin.",
      "output": "firmware operationId/state/receivedBytes/version/reason/remainingBudgetMs; состояния с receiving до booted/cancelled по разделу 14.",
      "errors": "invalidArgument, unsupportedVersion, incompatibleProfile, forbidden, staleBoot, busy, unsupported, notFound, resultExpired, storageFault",
      "behavior": "Повтор разрешен; это новое чтение, поэтому данные могут измениться. Если команда использует снимок, все его страницы читаются с одним snapshotId. Результат операции сохраняется в постоянном журнале загрузчика и доступен по operationId после смены bootId.",
      "maturity": "open-alert-draft/0.20",
      "obligation": "Дополнительная возможность"
    }
  ],
  "coreCommands": [
    "device.describe",
    "device.capabilities",
    "device.state",
    "inventory.get",
    "sensors.read",
    "events.read",
    "protocol.hello",
    "control.acquire",
    "control.resume",
    "control.release",
    "channel.ping",
    "session.start",
    "session.get",
    "session.stop",
    "session.cancel",
    "profiles.get"
  ],
  "requirements": [
    [
      "R01",
      "Реальное оповещение, учения, звучащая и тихая проверки",
      "Цель, права, допустимое физическое действие; учебный статус не означает тишину",
      "Учения во время реального оповещения"
    ],
    [
      "R02",
      "АРМ, резерв, местные кнопки, микрофон, внешние контакты",
      "Источники, идентификация, удержание и освобождение управления",
      "Местная кнопка во время сетевой записи; отпускание тангенты без АРМ"
    ],
    [
      "R03",
      "Право прерывания",
      "Текущий узел либо вышестоящий инициатор именно этого запуска через нижестоящий; высота и приоритет не дают права на чужой запуск",
      "Вышестоящий отменяет свой запуск; попытка остановить независимо запущенное нижестоящим оповещение отклоняется"
    ],
    [
      "R04",
      "Прибор, группа, отдельные выходы",
      "Адресные ресурсы с bindingGeneration; 1..4 ссылки, кодирование максимальных полей в пределах XML",
      "session.start записи с четырьмя ID по 32 символа и максимальными uint64 укладывается в 1024 байта"
    ],
    [
      "R05",
      "Сирена, запись, речь, последовательность",
      "Сирена: редакция аппаратного профиля, фазы и допуски; durationMs от running с паузами, maxDurationMs от acceptedAtMs. Составные программы отдельным расширением",
      "Неполный последний цикл, задержка подтверждения running, совпадение пределов, неподдержанная длительность; сирена, пауза и запись в профиле программы"
    ],
    [
      "R06",
      "Запись по номеру и обновление содержимого",
      "Локальная запись доступна до session.start; неизменные contentId/revision/hash, атомарное размещение, проверка и готовность. Передача файла во время оповещения является отдельным потоковым режимом",
      "Обрыв загрузки, смена содержимого слота и повтор session.start не запускают неполный/новый объект; M01"
    ],
    [
      "R07",
      "Исправление и отмена предупреждения",
      "Связь предупреждения АРМ с сеансами, область отмены, правила замены",
      "Исправленное сообщение приходит во время старого; отмена одного не останавливает другое"
    ],
    [
      "R08",
      "Немедленный групповой запуск",
      "Каждый получатель независимо принимает session.start в acceptBeforeMs; единый момент физического запуска по UTC в 0.20 не предоставляется",
      "Отказ одного получателя не превращается в общий успех; АРМ показывает точный состав принявших и исполнивших"
    ],
    [
      "R09",
      "Потеря управления при сирене или записи",
      "Настройка stop/finish на приборе, снимок ревизии на запуск; разрыв транспорта отдельно от lease; таблица переходов и непродлеваемые сроки",
      "Наблюдатель остается; resume до, на границе и после lease; восстановление при draining не разрешает новый session.start и не отменяет stopping"
    ],
    [
      "R10",
      "Потеря аудио, управления или обоих каналов",
      "Конечные сроки потери управления и аудио; небольшой ограниченный jitter-буфер микрофона, учет возраста пакета и политики восстановления",
      "Потери не скрываются буфером; поздний звук не воспроизводится, малый буфер проверяется на заданном канале; M03/M05/M07/M10"
    ],
    [
      "R11",
      "Повтор команды, потеря ответа",
      "Две границы приема, сохраненное решение, сравнение нормализованного содержимого до предметной валидации",
      "Повторы unsupported/invalidArgument возвращают прежний отказ; изменение ошибочного поля дает conflict"
    ],
    [
      "R12",
      "Питание, перезапуск, часы",
      "bootId, постоянное намерение до физического запуска, атомарная фиксация, обнаружение оборванной записи и восстановление unknown",
      "Отключение питания на каждой границе записи и воздействия; повреждение Flash не создает повторный запуск или ложный off"
    ],
    [
      "R13",
      "Отказ усилителя, линии, накопителя",
      "Доступные измерения, их свежесть, причина завершения",
      "Команда принята, усилитель не включился; нельзя утверждать, что население услышало сообщение"
    ],
    [
      "R14",
      "Обслуживание во время тревоги",
      "Допустимые настройки и загрузки, прерывание сервисных операций",
      "Срочный запуск во время загрузки файла или подготовки прошивки"
    ],
    [
      "R15",
      "Два АРМ, разделение сети",
      "Один активный экземпляр, acquireSeq/attachSeq, lease, освобождение и независимое ограждение резервируемого АРМ",
      "Потерян ответ acquire; две копии пытаются resume; возврат изолированной копии не дает ей доступа"
    ],
    [
      "R16",
      "Пропуски событий, устаревшее состояние",
      "events.read: afterSeq/nextSeq без snapshotId; снимки отдельно; обнаружение пропуска и история результата",
      "Повтор afterSeq при новых событиях и переполнении кольца; gap требует снимка, idle не доказывает успешного окончания"
    ],
    [
      "R17",
      "Замена прибора, ключей, отзыв доступа",
      "Индивидуальное доверие, поколения и отзыв ключей; российская защита хранения и переноса секретов",
      "Новый прибор по прежнему IP, плановая ротация с resume, отзыв старого ключа, утрата всех ключей и восстановление без возврата отозванного поколения."
    ],
    [
      "R18",
      "Подделка, повторы, перегрузка",
      "Российский TLS-профиль: Кузнечик-MGM, Стрибог-256, индивидуальный PSK, GC256A; ограниченные записи и обработка",
      "Чужой ключ, несовместимый криптонабор, поврежденная метка, повтор записи и исчерпание энтропии не приводят к исполнению или отключению защиты"
    ],
    [
      "R19",
      "Большая группа, слабый канал, multicast",
      "Без общего протокольного предела парка, с конечными квотами развертывания; RTP/UDP multicast для массовой речи, расчет unicast-полосы и pps, медленные ветви, явная модель доверия, медиаключи при их использовании и дозирование ответов",
      "1/10/100/1000/4000/10000 УУ; массовые ответы, допуск источника, смена группового ключа при его использовании, задержка и нагрузка реальных ветвей; M08/M09/M10"
    ],
    [
      "R20",
      "Обновление и восстановление",
      "Совместимый подлинный образ, права, допустимость отката, состояние загрузки",
      "Питание пропало при записи; подлинный образ для другой платы"
    ],
    [
      "R21",
      "Неизвестная функция или версия",
      "Ядро, capabilities, расширения, точные отказы",
      "Прибор без синхронного запуска НЕ ДОЛЖЕН вместо него запускаться немедленно"
    ],
    [
      "R22",
      "Разбор инцидента",
      "Участник, команда, сеанс, результат, качество времени; resultRetentionMs в одной загрузке отдельно от постоянного журнала восстановления и архива АРМ. Емкость покрывает скользящее окно завершений и резерв принятого исполнения; занятая история не задерживает следующий этап в пределах заявленной нагрузки",
      "Release не сокращает срок результата; reboot дает staleBoot, журнал сохраняет незакрытое воздействие; поздний запуск запрещен и после утраты подробного решения"
    ],
    [
      "R23",
      "Сторонний сервис и новый тип подключения",
      "Расширяемые адаптеры АРМ, общий контракт, идентичность, права, сопоставление событий и запусков",
      "Повтор webhook, перезапуск адаптера, отмена от другого сервиса, устаревшее сообщение после восстановления"
    ],
    [
      "R24",
      "Подтверждения узла, устройств и оператора",
      "Разные виды подтверждений, источник, объект и время; ручное подтверждение не заменяет телеметрию",
      "Узел принял запуск, часть устройств отказала, оператор подтвердил получение"
    ],
    [
      "R25",
      "Подготовленный тракт и фактический прием речи",
      "Готовность тракта, первый пакет, выдача отсчетов и физическое наблюдение различаются; три способа речи и приемка M01-M10; wire-медиапрофиль rtp-alert/4 задает END, validSamples, EOF и дрейф часов",
      "Готовый тракт без аудио не объявляет воспроизведение; потери середины/хвоста и EOF отражаются в полноте, включая отсутствие акустического доказательства; M02/M03/M04"
    ],
    [
      "R26",
      "Состав оборудования и датчиков",
      "Четыре facet точки, строгие presence/support/readMethod; enum и threshold с точными полями и связями",
      "Полный каталог без пропуска facet, повторов enum и порогов на несуществующие точки"
    ],
    [
      "R27",
      "Достоверность показаний",
      "Значение, источник, возраст и качество; полный выбранный набор sensors.read, недоступность отдельной записью",
      "Лишняя или потерянная точка и дубликат между страницами обнаруживаются; устаревшее значение не становится свежей нормой"
    ],
    [
      "R28",
      "Пассивный свежий опрос",
      "pollId, предел времени, ожидаемые точки, отдельные результаты и complete как признак покрытия",
      "Ответила половина источников; наблюдатель не удерживает связь владельца"
    ],
    [
      "R29",
      "Опросы без запуска и с запуском ОУ",
      "Два явных сценария; activationPoll через общий сеанс, фазы, права, полнота и подтвержденное выключение; ресурс с bindingGeneration, checks и observations со счетчиками. Для mode=test обязателен полный каталог tests.list по разделу 10",
      "ОУ включилось, измерения собраны, выключение неизвестно: общий успех запрещен; результат сопоставлен с session.start/accepted и фиксированным планом; пропуск записи запрещает успех"
    ],
    [
      "R30",
      "Аварийные эпизоды",
      "conditionId/alarmId, active/unknown в снимке, raised/cleared/qualityChanged в журнале, alarmCount и граница eventSeq; bool/int64/uint64/enum проверяются по типу и согласованному каталогу",
      "Подключение при действующей аварии и gap журнала восстанавливают полный активный набор без выдуманного нового фронта"
    ],
    [
      "R31",
      "Инженерная диагностика",
      "Слои контроллера, сети, ОУ, хранения и медиа; область счетчиков",
      "Перезапуск, насыщение счетчика, неизвестная crash capsule"
    ],
    [
      "R32",
      "Автономный мониторинг и массовые опросы",
      "Периоды источников, фильтры, квоты и бюджет совместно с речью",
      "Шторм изменений и несколько наблюдателей не блокируют управление"
    ],
    [
      "R33",
      "Согласованная телеметрия",
      "Нефильтруемый журнал; согласованный снимок с контрольными счетчиками, неизменными страницами и конечным восстановлением",
      "Пропуск аварии, смена ревизии, повтор cursor или смешение страниц обнаруживаются; неполный снимок не очищает аварии"
    ],
    [
      "R34",
      "Готовность отдельных функций",
      "Раздельно командная доступность и готовность сирены/записи/речи с причинами",
      "УУ отвечает, усилитель неисправен, часть фидеров недоступна"
    ],
    [
      "R35",
      "Функциональные части УУ",
      "Сетевой контракт АРМ/конфигуратора и требования к исполнительному тракту; внутренний интерфейс и число плат выбирает изготовитель",
      "Новый производитель не требует переопределения базовой команды АРМ"
    ],
    [
      "R36",
      "Независимые отказы сети и исполнительного тракта",
      "Раздельные состояния контроллера, взаимодействия с ОУ и самого ОУ в пределах объявленных наблюдений, без автоматического повторного session.start",
      "УУ на связи, ОУ не отвечает; затем взаимодействие с ОУ восстанавливается"
    ],
    [
      "R37",
      "Привязка и смена профиля ОУ",
      "ouId, драйвер, поколение привязки, каталог и реальные функции; известная АРМ и УУ редакция аппаратного профиля сигнала до session.start",
      "Поздний ответ прежнего ОУ после перенастройки не подтверждает новое исполнение"
    ],
    [
      "R38",
      "Собственные запросы УУ к пульту",
      "Двунаправленная корреляция, объявленные виды запросов, права и пределы ожидания",
      "Одновременно идут запрос АРМ и запрос УУ, ответы не смешиваются"
    ],
    [
      "R39",
      "Семейство совместимых профилей",
      "profileId/версия, зависимости, согласование обязательных функций и лимитов, единая семантика результатов",
      "Нет совместимой версии: управление не начинается и защита не ослабляется"
    ],
    [
      "R40",
      "Логическая цель и локальный адрес",
      "targetId отдельно от deviceId и locator, область сети, реестр привязок и выявление конфликтов",
      "Два одинаковых частных IP в разных муниципалитетах не смешивают команды и результаты"
    ],
    [
      "R41",
      "Контролируемый путь через АРМ",
      "Ответственный узел, ревизия маршрута, hopLimit, циклы и исходный срок приема",
      "Ретрансляция по циклу или после срока не создает поздний запуск"
    ],
    [
      "R42",
      "Предпусковая оценка конкретного плана",
      "readiness.get, возраст и источник доказательств, ready/degraded/blocked/unknown, быстрый повтор условий на session.start",
      "После ready изменились внутренний интерфейс, контент или привязка: старое заключение не разрешает запуск"
    ],
    [
      "R43",
      "Проверяемый манифест контента",
      "Локальный неизменный объект contentId/revision/hash до session.start; точные байты, streebog256, готовность и естественный конец; сетевое размещение использует полный контракт bulk/1.",
      "Обрыв размещения, неверный хеш, замена слота, неподтвержденный EOF; номер записи не равен verified; M01/M04"
    ],
    [
      "R44",
      "Каскадное наблюдение",
      "Отдельные права чтения, сквозная цель, источник и возраст исходного показания",
      "Посредник повторяет старый кеш и не делает его свежим"
    ],
    [
      "R45",
      "Сводка без утраты детализации",
      "Зафиксированный набор целей, взаимно исключающие статусы, ревизии, страницы первичных результатов",
      "Повтор, поздний ответ и два маршрута не увеличивают число успешно оповещенных целей"
    ],
    [
      "R46",
      "Обнаружение и доверенная регистрация",
      "Discovery отдельно от допуска, квоты, проверка идентичности, наладка привязки ОУ",
      "Найденное устройство или чужое объявление не получает полномочий управления"
    ],
    [
      "R47",
      "Переносимый пакет конфигурации",
      "Схема, ревизия, ссылки, манифесты, область полномочий, проверяемый импорт без секретов",
      "Импорт с отсутствующей записью, дубликатом цели или циклом отклоняется до применения"
    ],
    [
      "R48",
      "Согласованный состав групп",
      "Уполномоченный автор, groupRevision и digest, зафиксированное множество получателей",
      "Одноименная группа на другом АРМ имеет другой состав: молчаливая подмена запрещена"
    ],
    [
      "R49",
      "Фактическое применение и замена оборудования",
      "configRevision/appliedRevision/onControlLoss в обязательном device.state, контроль применения перед session.start",
      "Первый session.start без config.get: ревизии прочитаны из ядра; непримененная конфигурация дает unsafeState"
    ],
    [
      "R50",
      "Измеряемый паспорт масштаба",
      "Паспорт нагрузки и сети, отдельный heartbeat, локальный прием отмены, начало session.stop и подтверждение off; реестр limit, snapshotMaxPages/snapshotReadMaxMs, скорость журнала и квоты снимков",
      "TCP head-of-line blocking и полный отказ сети учитываются отдельно; finish не выдается за остановку; полный снимок укладывается в TTL с запасом, переход от теста к тревоге измерен также при отказе владельца"
    ],
    [
      "R51",
      "Этапы и проходы программы",
      "planRevision, stepId, проход, переходы, отмена таймеров, межэтапное владение; автономная программа отдельным профилем",
      "Поздний session.stop сирены после подготовки речи; отмена в паузе перед повтором записи; U01-U02"
    ],
    [
      "R52",
      "Тангента и право передавать речь",
      "Разрешение источника отдельно от владельца; запрос/выдача/удержание/освобождение/отзыв, конечный срок, новое поколение при новом нажатии; wire-схема audio.prepare/session.start/audio.finish и rtp-alert/4 фиксирует жизненный цикл",
      "Поздняя выдача, два источника, потеря говорящего, аудио после отпускания; U03, M05/M06/M09"
    ],
    [
      "R53",
      "Резервные пути к одному ОУ",
      "Единый физический ресурс и логическая цель, неизвестный результат первого пути, исключение двух исполнителей",
      "Ethernet-запуск выполнен без ответа, попытка повторить через внешний радиошлюз; U05"
    ],
    [
      "R54",
      "Неопределенность нижнего протокола",
      "Политика повторов каждого воздействия на ОУ, достаточность состояния, поздние ответы и восстановление",
      "session.start исполнен, ACK потерян; общий режим записи не подтверждает конкретный контент; U04"
    ],
    [
      "R55",
      "Срабатывание, снимок и повтор внешнего события",
      "Источник/эпизод, ID и ревизия правила, фронт/уровень, дребезг, повторное взведение, возврат питания",
      "Replay и снимок не являются новым запуском; активный вход после питания требует правила; U08-U09"
    ],
    [
      "R56",
      "Независимые реализации АРМ",
      "Логический узел отдельно от процесса и оператора; ядро, расширения, схемы, открытая приемка",
      "Разные АРМ одинаково трактуют состояния и отклоняют неизвестную обязательную функцию; U11"
    ],
    [
      "R57",
      "Отказ привязки медиасоединения",
      "Непривязанный канал не изменяет действующий поток; один экземпляр соединения на mediaId.",
      "Чужой principalId с верным mediaId, повтор того же владельца и поздний close старого канала."
    ],
    [
      "R58",
      "Учение перед реальной тревогой",
      "Техническая проверка ограничена 30000 мс; длительное учение до 1080000 требует независимого готового тракта тревоги.",
      "Проверяются обе границы, отсутствие независимого тракта, автоматическое освобождение и сохранение места real."
    ],
    [
      "R59",
      "Полный пакет локальных правил",
      "open-alert-config/5 содержит controllers и localRules, ссылки, полярность и правила edge/level.",
      "Отсутствующая политика, чужой deviceId, неизвестная программа или повтор входа отклоняют пакет."
    ],
    [
      "R60",
      "Устойчивый вход после reboot",
      "level оценивает новое физическое наблюдение после ready; edge ожидает повторного взведения.",
      "Активный уровень при загрузке, один запуск за эпизод, снятие ожидающего уровня, неизвестный вход."
    ],
    [
      "R61",
      "Достоверность успешного результата",
      "succeeded требует passed всех обязательных проверок и подтвержденного off всех выбранных ресурсов.",
      "Полный по счетчикам снимок с failed-проверкой, onObserved или чужой ссылкой отклоняется."
    ],
    [
      "R62",
      "Оконная передача файлов",
      "bulk/1: блок 4096..65536 байт, окно 4..64, явное согласование; DATA/ACK/ERROR и границы кадра.",
      "Четыре блока без ACK принимаются; выход за окно, пропуск, перекрытие, неверное направление/размер отклоняются."
    ],
    [
      "R63",
      "Устойчивый префикс загрузки",
      "ACK подтверждает сохраненные данные и журнал; незавершенный хвост не повреждает ранее подтвержденное.",
      "Потеря ACK и питания; новый hello возвращает устойчивый префикс; повреждение дает storageFault."
    ],
    [
      "R64",
      "Изоляция файлового канала",
      "Привязка к principalId, эпохе и экземплярам двух соединений; независимое обслуживание управления.",
      "Чужой/повторный hello и поздний callback не меняют передачу; real отзывает сервис за 500 мс."
    ],
    [
      "R65",
      "Сроки окна и памяти",
      "Бюджет по числу окон, ограничение буферов и непрерываемого шага; ACK не продлевает lease.",
      "Неполный DATA, медленный ACK, частичные подтверждения и бессодержательные повторы не снимают срок старейшего блока."
    ],
    [
      "R66",
      "Завершение и отмена файловой операции",
      "Полный ACK не равен staged/verified/committed. Проверка до 60000 мс согласована с idle, независимым lease и общими сроками; cancel и commit имеют определенные исходы.",
      "Проверка дольше 10000 мс завершается при живом lease; срок проверки, потеря lease и real-прерывание блокируют поздний результат; новая попытка не продлевает общие сроки."
    ],
    [
      "R67",
      "Подтверждение исполнения",
      "Принятие, начало работы, полнота и завершение передаются раздельно.",
      "accepted без воздействия; подтвержденная работа до session.stop; отмена до session.start; частичный запуск четырех ресурсов."
    ],
    [
      "R68",
      "Штатная остановка",
      "succeeded/operatorStop только для подтвержденной работы siren/recording/stream и off в срок без отказа.",
      "Остановленная речь имеет interrupted; остановленная программа не получает успех всей программы."
    ],
    [
      "R69",
      "Неисправность после запуска",
      "Историческое confirmed сохраняется, fault и аварийный итог не стираются.",
      "Потеря фазы после начала, session.stop после отказа, последующее снятие, неизвестный off и независимая авария чужой линии."
    ],
    [
      "R70",
      "Полномочия и занятость",
      "forbidden предшествует проверке занятости; причины отказов машиночитаемы.",
      "Нет роли, запрет области, чужой session.stop/resume; busy своего нового session.start; точный повтор; неизменность таймеров и highWater при forbidden."
    ],
    [
      "R71",
      "Предельные размеры",
      "Все команды и целые записи в максимально длинной допустимой сериализации укладываются в 1024 байта.",
      "Все действия, режимы session.start и readiness, четыре ресурса, восемь точек, uint64, UTF-8 и расширение XML-сущностей; 1025 байт отклоняются."
    ],
    [
      "R72",
      "Сирена до команды остановки",
      "stopMode=command не использует durationMs; независимый maxDurationMs и stop при потере управления.",
      "Нормальный session.stop, предел вместо session.stop, потеря lease, запрет command в автономной программе."
    ],
    [
      "R73",
      "Окно номера",
      "cmdSeqWindow=4096; отказ вне окна не расходует состояние.",
      "Границы highWater+4096/+4097, точный повтор, резерв session.stop/control.release, uint64 и новая эпоха."
    ],
    [
      "R74",
      "Привязка и резерв",
      "Живая привязка не вытесняется resume; внешнее ограждение остается обязательным.",
      "Живой, отсоединенный и истекший канал; отказ сохраняет attachSeq/lease; поздний обработчик; несколько УУ."
    ],
    [
      "R75",
      "Сигнал",
      "signalProfile ссылается на profiles; signalSpec на полный реестр сигналов config/5.",
      "Отсутствие, другая ревизия, неверный тип/частота, несогласованный паспорт отклоняются."
    ],
    [
      "R76",
      "Медиапривязка",
      "media.hello имеет явные ошибки и не вытесняет поток.",
      "Успех, чужой principalId, занятая привязка, потерянный ответ, неверный профиль."
    ],
    [
      "R77",
      "Доверие",
      "Первичное доверие закрепляется независимо; выдача секрета, staged, пробный TLS, активация и рабочее подключение различаются; импорт open-alert-trust/2 атомарен.",
      "Чистое устройство, неверная идентичность, секрет только на одной стороне, staged без управления, потеря подтверждения активации, откат поколения и обрыв питания."
    ],
    [
      "R78",
      "Подпись и издатель",
      "Кодирование подписи однозначно; отзыв перепроверяется до commit.",
      "Проверочные значения двумя реализациями; перестановки r/s, измененный манифест, отозванный издатель."
    ],
    [
      "R79",
      "Единицы и сроки",
      "UTF-8 ограничен байтами; session.stop и backoff имеют конечные границы.",
      "128/129 байт, непарный суррогат, общий/технический session.stop, шторм переподключений и независимый слот отмены."
    ],
    [
      "R80",
      "Согласованность комплекта",
      "Общие XML, закрытые причины и все артефакты согласованы с манифестом.",
      "exercise/stop, programAborted только notStarted, ссылки на текущие digest/signature-векторы."
    ],
    [
      "R81",
      "Открытый и защищенный обмен",
      "Режим plain задается явно; secureOnly запрещает открытое управление.",
      "Чистое УУ при optional; неверный TLS не становится plain; открытый канал не наследует TLS-владение."
    ],
    [
      "R82",
      "Парное доверие без секретов в пакете",
      "Пакет доверия не содержит секретных ключей.",
      "Схема отвергает pskHex; изменение deviceId/principalId/rootId меняет PSK; сверка KDF с векторами."
    ],
    [
      "R83",
      "Свежесть речевых пакетов",
      "rtp-alert/4 ограничивает возраст каждого выдаваемого пакета.",
      "Задержки TCP, истечение часов, подмена метки, тишина и session.stop; отдельно для tls и plain."
    ],
    [
      "R84",
      "Допуск handshake",
      "Неаутентифицированные TCP-сокеты не занимают контексты PSK-DHE.",
      "Два молчащих подключения, повтор ClientHello и неверный binder не занимают дорогой резерв владельца."
    ],
    [
      "R85",
      "Слоты и кеш решений",
      "Чтения и heartbeat не расходуют cmdSeq; резервные номера обходят обычное окно.",
      "Повторы ping, занятый слот, скачок выше окна и гонки session.start/session.cancel."
    ],
    [
      "R86",
      "Повтор освобождения",
      "Новый канал того же участника возвращает сохраненное решение control.release без владения.",
      "Принятый и непринятый release, новый канал, смена principalId, режима и bootId."
    ],
    [
      "R87",
      "Канал от 64 кбит/с",
      "Сжатая речь, управление и накладные расходы укладываются в рассчитанную полосу.",
      "GSM/80, задержки и потери, пауза фоновой передачи, верхний срок session.stop."
    ],
    [
      "R88",
      "Групповая речь",
      "Подпись источника проверяется отдельно от общего ключа; ACK на пакет запрещены.",
      "Подделка получателем, одна и две потери частей, повтор, END, отзыв и сбор результатов."
    ],
    [
      "R89",
      "Схема и примеры",
      "XSD, поля запросов, закрытые словари и все команды имеют проверяемое покрытие.",
      "Неверный status, неизвестное поле, чужой to, обязательные начальные состояния и сквозные трассы."
    ]
  ],
  "references": [
    [
      "ГОСТ Р 42.3.05-2023. Протоколы информационного обмена. Общие требования",
      "https://meganorm.ru/mega_doc/norm/gost-r_gosudarstvennyj-standart/8/gost_r_42_3_05-2023_natsionalnyy_standart_rossiyskoy.html"
    ],
    [
      "Федеральный закон № 162-ФЗ, статья 26. Правила применения документов стандартизации",
      "https://base.garant.ru/71108018/3ac805f6d87af32d44de92b042d51285/"
    ],
    [
      "RFC 5740. Надежная групповая доставка и ограничение обратной связи",
      "https://www.rfc-editor.org/rfc/rfc5740.html"
    ],
    [
      "RFC 8785: каноническое представление JSON",
      "https://www.rfc-editor.org/rfc/rfc8785.html"
    ],
    [
      "Р 1323565.1.030-2020 и RFC 9367: российские алгоритмы в TLS 1.3",
      "https://tc26.ru/news/novosti-tk26/opublikovan-rfc-9367-opredelyayushchiy-poryadok-raboty-tls-1-3-s-gost.html"
    ],
    [
      "RFC 9367: российские криптонаборы TLS 1.3",
      "https://www.rfc-editor.org/rfc/rfc9367.html"
    ],
    [
      "RFC 7801: Кузнечик",
      "https://www.rfc-editor.org/rfc/rfc7801.html"
    ],
    [
      "RFC 9058: MGM",
      "https://www.rfc-editor.org/rfc/rfc9058.html"
    ],
    [
      "RFC 6986: Стрибог",
      "https://www.rfc-editor.org/rfc/rfc6986.html"
    ],
    [
      "RFC 7836: российские функции выработки ключей",
      "https://www.rfc-editor.org/rfc/rfc7836.html"
    ],
    [
      "RFC 8449: ограничение размера записей TLS",
      "https://www.rfc-editor.org/rfc/rfc8449.html"
    ],
    [
      "RFC 9325: безопасное применение TLS",
      "https://www.rfc-editor.org/rfc/rfc9325.html"
    ],
    [
      "RFC 2119 и нормативные уровни требований",
      "https://www.rfc-editor.org/rfc/rfc2119.html"
    ],
    [
      "RFC 8174: применение нормативных ключевых слов",
      "https://www.rfc-editor.org/rfc/rfc8174.html"
    ],
    [
      "RFC 8446: TLS 1.3",
      "https://www.rfc-editor.org/rfc/rfc8446.html"
    ],
    [
      "RFC 7250: raw public keys",
      "https://www.rfc-editor.org/rfc/rfc7250.html"
    ],
    [
      "RFC 9147: DTLS 1.3",
      "https://www.rfc-editor.org/rfc/rfc9147.html"
    ],
    [
      "RFC 3711: SRTP",
      "https://www.rfc-editor.org/rfc/rfc3711.html"
    ],
    [
      "RFC 3552: угрозы, аутентификация и полномочия",
      "https://www.rfc-editor.org/rfc/rfc3552.html"
    ],
    [
      "RFC 8342: желаемая конфигурация и операционное состояние",
      "https://www.rfc-editor.org/rfc/rfc8342.html"
    ],
    [
      "CAP 1.2: модель предупреждения",
      "https://docs.oasis-open.org/emergency/cap/v1.2/CAP-v1.2-os.html"
    ],
    [
      "EDXL-DE 2.0: распространение сообщений",
      "https://docs.oasis-open.org/emergency/edxl-de/v2.0/edxl-de-v2.0.html"
    ],
    [
      "XML 1.0: синтаксис",
      "https://www.w3.org/TR/xml/"
    ],
    [
      "RFC 9293: TCP",
      "https://www.rfc-editor.org/rfc/rfc9293.html"
    ],
    [
      "RFC 3551: форматы PCMA и GSM для RTP",
      "https://www.rfc-editor.org/rfc/rfc3551.html"
    ],
    [
      "RFC 4571: обрамление RTP в соединении TCP",
      "https://www.rfc-editor.org/rfc/rfc4571.html"
    ],
    [
      "RFC 3550: RTP",
      "https://www.rfc-editor.org/rfc/rfc3550.html"
    ],
    [
      "RFC 6241: NETCONF, пример транзакционной настройки",
      "https://www.rfc-editor.org/rfc/rfc6241.html"
    ],
    [
      "RFC 5116: AEAD",
      "https://www.rfc-editor.org/rfc/rfc5116.html"
    ]
  ],
  "currentRevisionChanges": "Редакция 0.20 устраняет расхождения срока TLS, размера GSM/80, первой страницы состояния и диапазона executionCount. Машинные схемы проверяют специальные числовые пределы; общий размер конфигурации применяется совместно с квотами коллекций. Исправлены реестр XML и проверка detail, согласованы ошибки команд. Каждое требование связано с нормативными положениями и номерами пунктов PDF. Полный групповой профиль включен в нормативное приложение Ж единого PDF. Введение начинается с назначения документа; преимущества предшествуют техническому описанию. Уровни требований выражены русскими нормативными словами, выделенными прописными буквами. Групповой профиль уточняет IGMP, фильтрацию маршрута, возраст каждого выдаваемого отсчета и позднее обнаружение ошибки XOR. Заданы квота handshake на участника, точные PSK identity, форматы ключей и подписи доверия; запрет отката сохраняется после подтверждения. Условия использования закрепляют сохранение предоставленных прав и собственные патентные обязательства лицензиара. Режимы optional/plain, русское наименование и ориентир конечного канала от 64 кбит/с сохранены. Дополнительная корректура 11 октября согласует 60-секундную проверку файла с владением и lease, запрещает прием UTF-16/UTF-32 вместо UTF-8 и сочетания test с real/exercise. Срок сборки кадра равен 10000 мс, отказ в правах использует status=error, а network.confirm сохраняет режим и участника network.stage. Эти исправления обязательны для соответствия актуальному комплекту; прежний манифест 0.20 не подтверждает их выполнение. Последующая корректура уточняет единый состав alarmTransitionMaxMs с ожиданием повторного acquire и приемом session.start, расчет числа записей TLS и исполнение принятой программы при finish. Проверка XML применяет поля каждого режима, длительности и сочетания параметров audio.prepare. Схемы конфигурации и межузлового обмена представлены отдельными таблицами объектов с одним полем в строке; варианты объектов и вложенные записи разделены. Публичный комплект содержит только текущую редакцию. Дополнение 12 октября связывает существующие правила доверия и TLS в универсальную процедуру первичного ввода, выдачи ключей, активации, повторного подключения, ротации и восстановления. Уточнены результаты шагов, действия после обрыва и состав сведений в паспорте изделия; новые сетевые команды и форматы не вводятся.",
  "multicast": {
    "revision": "0.20",
    "profile": "group-audio/1",
    "openProfile": "group-audio-open/1",
    "title": "Открытый протокол систем оповещения. Групповая передача речи",
    "codec": "GSM",
    "sampleRate": 8000,
    "blockMs": 400,
    "shardMs": 100,
    "dataShards": 4,
    "parityShards": 1,
    "shardBytes": 165,
    "headerBytes": 40,
    "signatureBytes": 64,
    "tagBytes": 16,
    "maxDatagramBytes": 285,
    "maxAgeMs": 5000,
    "clockErrorMaxMs": 500,
    "maxBufferedBlocks": 4,
    "initialBufferMs": 800,
    "audioAbsenceMs": 5000,
    "verificationRatePerSecond": 25,
    "sections": [
      {
        "id": "group-scope",
        "title": "1. Назначение и совместимость",
        "blocks": [
          {
            "type": "p",
            "text": "Групповая передача дополняет базовую одноадресную связь с каждым УУ. Она предназначена для одной речи, одновременно адресованной многим получателям, когда сеть поддерживает маршрутизацию групп IPv4. Групповой пакет не выдает владение, не запускает ОУ и не заменяет индивидуальные результаты. Обязательная совместимость основного протокола не требует поддержки этого расширения."
          },
          {
            "type": "p",
            "text": "group-audio/1 использует UDP, шифрование Кузнечик-MGM и подпись источника ГОСТ Р 34.10-2012/GC256A со Стрибог-256. Общий ключ потока обеспечивает конфиденциальность, а отдельная подпись доказывает источник и при компрометации получателя. group-audio-open/1 передает те же заголовок и звуковые блоки без шифрования, метки и подписи; он ДОПУСКАЕТСЯ только при явно выбранном plain и optional. Эти режимы не равноценны по защищенности. Автоматическое переключение между ними запрещено."
          },
          {
            "type": "p",
            "text": "Маршрутизаторы и узлы распределения могут копировать исходные датаграммы без их изменения. Перекодирование или новое подписание создает другой источник и новый поток с отдельным разрешением. Для защищенного варианта подпись исходного разрешенного источника проверяет каждый УУ до декодирования. Доверие промежуточному узлу не заменяет эту проверку."
          }
        ]
      },
      {
        "id": "group-config",
        "title": "2. Конфигурация и подготовка",
        "blocks": [
          {
            "type": "p",
            "text": "Неизменный объект mediaGroups пакета конфигурации задает id, revision, deviceId, sourcePrincipalId, security=tls|plain, sourceAddress, sourcePort, groupAddress, destinationPort, ttl и resources. Только при security=tls обязательны sourceKeyId, publicX, publicY; при plain эти поля запрещены. id/sourceKeyId — token до 32 байт; revision — uint64 больше нуля; идентичности — 32 строчных hex; publicX/publicY — 64 строчных hex целочисленных координат GC256A в порядке big-endian, проверяемые на принадлежность кривой и подгруппе. resources содержит 1..4 различных resourceId/bindingGeneration данного deviceId; физическая привязка проверяется до commit. Открытые ключи не являются секретами. Изменение защищенного шаблона, источника и его ключа требует дополнительно права trustAdmin по TLS либо местной процедуры ввода при off. Открытый канал не устанавливает такое доверие; изменение иных полей конфигурации не дает обхода этой проверки."
          },
          {
            "type": "p",
            "text": "sourceAddress — разрешенный одноадресный IPv4; groupAddress — административная группа 239.0.0.0/8; sourcePort/destinationPort — 1..65535, ttl — 1..255. Сеть явно разрешает маршрут от этого источника до группы. До успешного ответа audio.group.prepare УУ выбирает локальный приемный интерфейс, открывает UDP-прием и выполняет присоединение IGMPv3 INCLUDE(sourceAddress) к groupAddress; стек ДОЛЖЕН принять подписку и поставить первоначальный отчет в очередь отправки. Если сеть использует IGMPv2, ДОПУСКАЕТСЯ присоединение к группе с обязательной программной фильтрацией источника. Неудача местного присоединения дает sourceUnavailable, без prepared. Это подтверждает местную подготовку, а не доставку по всем коммутаторам: фактический прием блока 0 проверяется в пределах prepareTimeoutMs. Источник начинает поток после получения prepared от выбранных получателей; неподготовленный получатель исключается явно. До session.start УУ не выдает звук. После discard, истечения подготовки, завершения, отзыва или reboot подписка снимается, когда не осталось другого действующего местного контекста этой группы. IGMP и фильтрация адреса не доказывают автора. Запоздалое присоединение к уже звучащему потоку в этом профиле НЕ ДОПУСКАЕТСЯ: ТРЕБУЕТСЯ новая подготовка и новый поток."
          },
          {
            "type": "p",
            "text": "audio.group.prepare является изменяющей командой владельца. Ее единственный элемент group содержит id, revision, streamId, source=recording|microphone и, только для recording, totalSamples. В режиме tls обязателен key — 64 строчных hex нового случайного 256-битного ключа данного потока; при plain key запрещен. Группа выбирает ресурсы из конфигурации, поэтому запрос не повторяет их длинное описание. Действуют bootId/controlEpoch/cmdSeq, права на все ресурсы и равенство sourcePrincipalId текущему участнику; для plain сравнивается только объявленный clientId без доказательства личности."
          },
          {
            "type": "p",
            "text": "Режим шаблона совпадает с режимом управляющего соединения; несовпадение дает forbidden. УУ выбирает group-audio/1 при tls и group-audio-open/1 при plain, проверяет поддержку соответствующего профиля, конфигурацию, часы и квоты, затем возвращает prepared mediaId/expiresAtMs/configRevision/capabilityRevision. mediaId индивидуален для УУ; streamId общий у получателей одного потока, содержит 32 строчных hex и создается случайно заново при каждой подготовке источника. Один streamId не используется повторно. Срок подготовки — 60000 мс. Повтор команды не продлевает срок. Во всех получателях один streamId обозначает одинаковые кодек, источник, ключ и шкалу блоков; иное значение отвергается как conflict."
          },
          {
            "type": "p",
            "text": "group-audio/1 РАЗРЕШАЕТСЯ только с защищенным управлением. Ключ передается каждому УУ по его собственному TLS-соединению с парным секретом, не включается в публичные пакеты доверия, конфигурацию, журналы и диагностические выгрузки. Хранятся лишь необходимый оперативный ключ и хеш нормализованного запроса для дедупликации; ключ уничтожается при discard, завершении, отзыве, истечении подготовки или reboot. Он не является общим секретом парка и не используется для управления, другой группы или нового потока. Приватный ключ подписи остается только у источника."
          },
          {
            "type": "p",
            "text": "audio.state дополнительно содержит groupStats с uint64 recoveredParts, receivedParityParts и missingDataParts. receivedPackets считает принятые проверенные звуковые части 0..3, receivedParityParts — части 4; FEC-восстановление увеличивает recoveredParts, а невосстановленный к сроку пропуск — missingDataParts и lostPackets. Поздние и повторные датаграммы учитываются отдельно и не увеличивают принятые части. clock имеет общие показатели частоты ЦАП; отсутствие оценки до начала звука не подменяется нулевой погрешностью. Индивидуальный session.get сохраняет критерии исполнения и полноту звука."
          },
          {
            "type": "p",
            "text": "session.start(mode=stream) с полученным mediaId и точными resources запускает обычный автомат исполнения; onControlLoss=stop. audio.discard, audio.state, audio.finish и session.stop/cancel сохраняют общие назначения. media.hello для группового профиля не используется. В audio.finish lastPacketIndex означает последний blockIndex, а sampleCount — число отсчетов источника. У каждого получателя session.start, session.stop, ошибки и результат самостоятельны; потеря связи с одним не создает успех остальным."
          }
        ]
      },
      {
        "id": "group-format",
        "title": "3. Формат датаграммы и подпись источника",
        "blocks": [
          {
            "type": "table",
            "headers": [
              "Смещение / длина",
              "Поле и представление"
            ],
            "rows": [
              [
                "0 / 4",
                "ASCII GA01 для защищенного профиля, GO01 для открытого."
              ],
              [
                "4 / 16",
                "streamId: 16 байт, полученных декодированием 32 hex."
              ],
              [
                "20 / 4",
                "blockIndex: uint32 BE, начинается с 0, увеличивается на 1; переполнение запрещено."
              ],
              [
                "24 / 8",
                "capturedUtcMs: uint64 BE, время захвата первого отсчета блока по UTC источника."
              ],
              [
                "32 / 2",
                "validSamples: uint16 BE, число действительных отсчетов всего блока: 3200, кроме конечного."
              ],
              [
                "34 / 2",
                "clockErrorMs: uint16 BE, верхняя погрешность UTC источника, 0..500."
              ],
              [
                "36 / 1",
                "shardIndex: 0..3 для звуковых частей, 4 для восстановительной части."
              ],
              [
                "37 / 1",
                "flags: бит 0 — END; остальные биты равны нулю."
              ],
              [
                "38 / 2",
                "Резерв: два нулевых байта."
              ],
              [
                "40 / 165",
                "Защищенный профиль: шифротекст; открытый профиль: звуковая или восстановительная часть."
              ],
              [
                "205 / 16",
                "Только защищенный профиль: полная метка MGM."
              ],
              [
                "221 / 64",
                "Только защищенный профиль: подпись r32 LE || s32 LE, как в общем профиле подписи."
              ]
            ]
          },
          {
            "type": "p",
            "text": "Длина UDP payload равна ровно 285 байтам при GA01 и 205 при GO01; иных длин, расширений или IP-фрагментации нет. MTU тракта ДОЛЖЕН вмещать датаграмму с IP/UDP и применяемой инкапсуляцией. Алгоритмы подписи, проверки точки, диапазонов r/s и хеша совпадают с основным документом. Подписывается ASCII OA-GROUP/1, затем нулевой байт, затем первые 221 байт датаграммы в исходном порядке. Подпись не включает сама себя. Перевод текста, пересериализация заголовка и разворот половин подписи запрещены."
          },
          {
            "type": "p",
            "text": "Для MGM применяются ключ потока, полный 16-байтовый тег, associated data — заголовок 40 байт, открытый текст — часть 165 байт. ICN равен конкатенации семи нулевых байтов, blockIndex:uint32 BE, shardIndex:uint8 и четырех нулевых байтов: 16 байт, старший бит нулевой. Срок одного потока не превышает 86400000 мс и 216000 блоков; дальнейшая речь требует новых streamId, ключа и подготовки. Уникальность ICN обеспечивается парой blockIndex/shardIndex при новом ключе каждого потока. Повторная передача допускает только побайтовую копию прежней датаграммы; повторное шифрование другого содержимого под тем же ICN запрещено."
          },
          {
            "type": "p",
            "text": "Порядок приема: проверка входного интерфейса и точного совпадения IPv4 sourceAddress, destination=groupAddress, UDP sourcePort и destinationPort с подготовленным шаблоном; затем длина и фиксированные поля, наличие действующего streamId, допустимое окно индексов и времени, затем проверка подписи разрешенного sourceKeyId, затем MGM, затем размещение части. До успешных криптографических проверок не обновляются окно повторов, таймер аудио и состояние воспроизведения. Знание группового ключа, IP-адреса, идентификатора потока или метки MGM не заменяет подпись. Открытый профиль сохраняет проверки формата и контекста, но не дает гарантии происхождения или целостности."
          }
        ]
      },
      {
        "id": "group-recovery",
        "title": "4. Блоки, восстановление потерь и очереди",
        "blocks": [
          {
            "type": "p",
            "text": "Кодек — GSM 06.10, RFC 3551, 8000 Гц, один канал. Блок охватывает 400 мс, то есть 3200 отсчетов и 20 кодековых кадров по 33 байта. Он разделяется на четыре последовательные части по 165 байт: пять кадров, 800 отсчетов и 100 мс. Часть 4 равна побайтовому XOR четырех открытых звуковых частей. Заголовки пяти частей имеют одинаковые streamId, blockIndex, capturedUtcMs, validSamples, clockErrorMs и flags, различается только shardIndex. Все пять частей отдельно защищаются."
          },
          {
            "type": "p",
            "text": "Без потерь воспроизводятся проверенные части 0..3. При потере ровно одной звуковой части и наличии трех других плюс части 4 восстанавливаются ее 165 байт XOR. Доказательство источника восстановленных байтов состоит из подписанных и прошедших MGM трех частей и восстановительной части того же блока; отдельная выдуманная подпись отсутствующей части не создается. Общие поля каждой новой проверенной части сверяются с уже принятой частью того же блока. Если все пять частей доступны до выдачи, до первого отсчета проверяется нулевой XOR. Несогласованные поля или ненулевой XOR означают ошибку источника: дальнейшая выдача прекращается, невоспроизведенные очереди очищаются, сеанс завершается failed/mediaIncomplete при подтвержденном off. Уже выданный звук не отменяется задним числом и сохраняется как состоявшееся исполнение с неполным завершением. Четыре целые проверенные звуковые части разрешено воспроизвести без ожидания восстановительной; ее отсутствие само по себе не является ошибкой. Если противоречие обнаружено после начала выдачи еще актуального блока, применяется то же прекращение остатка; опоздавшая часть уже закрытого блока отбрасывается без пересмотра законченной истории. Две потери этим кодом не восстанавливаются. Подпись общего ключа группы не используется: получатель не имеет ключа подписи источника."
          },
          {
            "type": "p",
            "text": "Источник накапливает один блок и отправляет пять частей равномерно с интервалом 80 мс, начиная не позднее 400 мс от первого захваченного отсчета. Захват следующего блока идет параллельно; в сети передается 12,5 датаграммы в секунду. Паузы микрофона кодируются как звук тишины, VAD/DTX запрещены. После захвата последнего частичного блока он отправляется по тому же порядку; END=1, validSamples=0..3200. Последний используемый GSM-кадр дополняется тишиной до 160 отсчетов, неиспользуемые кадры и части содержат нулевые байты и не декодируются. При нулевой речи передается конечный блок 0 с validSamples=0."
          },
          {
            "type": "p",
            "text": "Получатель хранит не более четырех блоков и 20 проверенных частей. Положение блока задается blockIndex*3200; заполнение начинается с блока 0. Начальный буфер 800 мс, максимум 1600 мс звука; отдельно считаются заголовки, защита, очереди и ЦАП. Окно принимает индексы от nextBlock до nextBlock+3. Поздний блок ниже nextBlock не воспроизводится; дальний индекс не сдвигает окно. Неверная подпись не резервирует индекс. Входная очередь до проверки — не более 5 датаграмм, приемник обеспечивает 25 проверок подписи в секунду с сохранением обслуживания session.stop. Это проверяемая квота, а не обещание неуязвимости к насыщению канала."
          },
          {
            "type": "p",
            "text": "Восстановление выполняется только до срока выдачи соответствующих отсчетов. Пропуск, который уже нельзя восстановить, маскируется тишиной и учитывается один раз в lostPackets и итоговой полноте; позднее прибытие не повторяет звучание. FEC-восстановленная часть учитывается как восстановленная и не является потерей звука. Повторных запросов NACK и подтверждений ACK на медиадатаграммы нет. Удаление невоспроизведенных данных из-за переполнения не скрывается: поток прекращается с mediaIncomplete, подтверждается off."
          }
        ]
      },
      {
        "id": "group-time",
        "title": "5. Время, окончание и отзыв",
        "blocks": [
          {
            "type": "p",
            "text": "Источник и УУ имеют UTC с подтвержденной верхней погрешностью не более 500 мс. Способ синхронизации фиксируется в паспорте и проверяется на фактическом канале; при tls источник времени ДОЛЖЕН быть аутентифицирован либо локально доверен. Просто полученная метка времени без учета задержки не доказывает эту погрешность. При отсутствии доказанной границы подготовка получает sourceUnavailable; утрата границы во время работы дает mediaClockFault. Возраст оценки часов вместе с дрейфом включается в публикуемую границу; неподтвержденное NTP-время не считается точным."
          },
          {
            "type": "p",
            "text": "Верхняя оценка возраста первого отсчета A=nowUtcMs+receiverErrorMs-capturedUtcMs+clockErrorMs. При A<-2 либо A>5000 блок отвергается; вычисления знаковые, без переполнения. При выдаче проверяется возраст именно выдаваемых отсчетов, с учетом их положения в блоке. Для последнего действительного отсчета части j=min(validSamples,800*(shardIndex+1))-1, где j — номер от начала блока. Его расчетное время захвата capturedUtcMs+j/8 мс; верхнее время фактической выдачи ЦАП обозначено emitUtcMs+receiverErrorMs. Проверяется -16 <= 8*(emitUtcMs+receiverErrorMs-capturedUtcMs+clockErrorMs)-j <= 40000; арифметика целочисленная знаковая без округления. Проверка распространяется на все отсчеты, включая первый; заранее рассчитанный срок учитывает аппаратную очередь, ресемплинг и дрейф. При нарушении отсчет не выдается, поток прекращается с mediaClockFault. Для части с нулем действительных отсчетов проверки выдачи нет. Для восстановленной части используется ее индекс0..3, а не индекс4 пакета XOR. При откате UTC либо скачке, нарушающем подтвержденную границу, сеанс останавливается с mediaClockFault; уже начатая остановка не отменяется исправлением часов. capturedUtcMs отражает захват звука, а не отправку очереди."
          },
          {
            "type": "p",
            "text": "END и audio.finish задают одну конечную границу: sampleCount=lastPacketIndex*3200+validSamples конечного блока. Для recording она совпадает с totalSamples подготовки. Противоречие дает conflict для команды либо mediaIncomplete для потока. Достаточно одного принятого объявления границы, однако успех полного воспроизведения требует всех отсчетов до нее, полученных либо восстановленных в срок. audio.finish повторяется индивидуально до подтверждения в общем порядке; потеря END не превращается в бесконечное ожидание. После первого принятого конца отводится не более 5000 мс на данные и выдачу хвоста, при сохранении maxDurationMs и предела возраста. Короткий полный поток начинает выдачу после получения всех его частей без ожидания 800 мс."
          },
          {
            "type": "p",
            "text": "Непрерывное отсутствие пригодного нового аудио 5000 мс вызывает sourceUnavailable для микрофона либо mediaIncomplete для записи. Подпись дубликата, FEC без новых пригодных отсчетов и служебный опрос не продлевают этот срок. Каждый УУ применяет свой lease и session.stop; пакет группы не возобновляет отозванный mediaId. session.stop очищает звуковые и аппаратные очереди в физических бюджетах основного документа, без дренирования."
          },
          {
            "type": "p",
            "text": "При исключении получателя или компрометации ключа источник прекращает прежний поток, создаются новые streamId и ключ, оставшиеся получатели проходят новую индивидуальную подготовку и session.start. Передача нового звука со старым ключом после отзыва запрещена. Отозванный публичный ключ источника прекращает прием немедленно. Утерянное подтверждение session.stop сохраняет неопределенный физический результат данного УУ; новое разрешение не отменяет блокировку его ресурса."
          }
        ]
      },
      {
        "id": "group-scale",
        "title": "6. Полоса и обратная связь",
        "blocks": [
          {
            "type": "p",
            "text": "Защищенная датаграмма требует 285 байт UDP payload, 8 UDP, 20 IPv4 и 38 Ethernet с преамбулой и межкадровым интервалом: 351 байт на тракте без VLAN и туннелей. Пять частей на 400 мс, включая восстановительную, дают 35,1 кбит/с. С резервом 30% — 45,63 кбит/с; с выделенными 12 кбит/с управления — 57,63 кбит/с. Открытый вариант: 271 байт, 27,1 кбит/с, 35,23 с резервом. Дополнительные заголовки и фактические потери включаются в расчет отдельно. Если они превышают остаток, данный поток на канале 64 кбит/с НЕ ДОПУСКАЕТСЯ."
          },
          {
            "type": "p",
            "text": "На общем участке маршрутизируемого группового дерева передается одна копия потока, на конечных ветвях — по одной. Число получателей не умножает исходящий медиатрафик источника, но влияет на состояние маршрутизаторов, команды подготовки и результаты. Протокол не обещает одновременно передать тысячи индивидуальных команд через один общий канал 64 кбит/с за срок одного кадра. Для таких групп рассчитываются общие участки, параллельная подготовка и емкость узлов распределения; сам по себе multicast не сокращает адресное управление."
          },
          {
            "type": "p",
            "text": "Начало потока не порождает массового группового запроса ответа. Готовность, ошибки, audio.state и итог читаются по индивидуальным управляющим соединениям с ограничением частоты у АРМ. На каждом узком участке резервируется полоса управления; опросы распределяются по времени с равномерным случайным смещением в интервале опроса, session.start/session.stop имеют приоритет. Итоговый отчет содержит также устройства, от которых результата нет. Ни размер группы, ни отсутствие сообщения об ошибке не считаются квитанцией успеха."
          }
        ]
      },
      {
        "id": "group-tests",
        "title": "7. Критерии приемки",
        "blocks": [
          {
            "type": "list",
            "ordered": true,
            "items": [
              "Проверяются точные байты заголовка, MGM, подписи и ICN, несовпадение streamId, измененная половина подписи, неверная точка ключа и повтор ICN с измененным содержимым. Получатель с групповым ключом и без приватного ключа источника не создает принимаемую поддельную речь.",
              "Для каждого из четырех положений теряется одна часть и восстанавливаются ровно исходные байты. Проверяются две потери, повреждение parity, повторы, перестановка, далекий индекс, позднее восстановление, конечный частичный и пустой блок.",
              "Проверяются пределы возраста и ошибки UTC, задержка очередей до ЦАП, потеря синхронизации, непрерывная тишина, потеря END и audio.finish, session.stop во время накопления и выдачи, отзыв одного получателя и источника.",
              "На фактическом тракте измеряются скорости обоих направлений, задержка, полоса управления, 25 проверок подписи в секунду и аппаратные сроки. Испытание группы проверяет отсутствие ответов на каждый медиапакет и индивидуальную полноту итогового отчета."
            ]
          }
        ]
      }
    ]
  }
}
