feat: живой цикл M2M с НРД + мастер установки ключа на флешку

Инфраструктура M2M (живой обмен с НРД через ИШ):
- обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение
  ответа; INFO → ждём Decision; идемпотентность поллера
- fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO
- сырой XML ответа НРД в карточке заявки (для пересылки в ТП)
- тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes,
  4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта
- редирект из теста сразу в карточку заявки

Мастер установки ключа Валидаты на флешку (admin/setup/keywizard):
- пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник
  сертификатов (CRL) → перезапуск+проверка ИШ → готово
- привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен,
  bj-server остаётся в песочнице
- сохранение структуры профиля архива (spr<N>), перечисление съёмных USB

Прочее:
- пакет-доказательство для ТП НРД + форма регистрации участника M2M
- эталонные образцы робота (DOC/m2m_robot_samples)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-06-19 00:03:21 +03:00
parent 6e503433d4
commit 9737c787f9
110 changed files with 10771 additions and 1690 deletions
+28
View File
@@ -1,6 +1,7 @@
package m2m_test
import (
"encoding/xml"
"errors"
"os"
"path/filepath"
@@ -35,6 +36,24 @@ var roundTripCases = []roundTripCase{
{filepath.Join("..", "..", "DOC", "Эталонные сообщения", "M2MTransferDecision_эталон.xml"), func() any { return new(m2m.M2MTransferDecision) }},
}
// clearXMLNameSpace обнуляет поле Space у верхнеуровневого xml.Name (если
// есть), чтобы round-trip-сравнение не зависело от namespace корня входного
// документа.
func clearXMLNameSpace(v any) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return
}
rv = rv.Elem()
if rv.Kind() != reflect.Struct {
return
}
f := rv.FieldByName("XMLName")
if f.IsValid() && f.CanSet() && f.Type() == reflect.TypeOf(xml.Name{}) {
f.Set(reflect.ValueOf(xml.Name{Local: f.Interface().(xml.Name).Local}))
}
}
func TestRoundTrip(t *testing.T) {
for _, c := range roundTripCases {
c := c
@@ -70,6 +89,15 @@ func TestRoundTrip(t *testing.T) {
t.Fatalf("unmarshal после Marshal: %v", err)
}
// XMLName.Space — это метаданные пространства имён входного XML,
// а не полезная нагрузка. Часть документов НРД присылает с
// namespace на корне (официальные примеры), часть — без (реальный
// ответ робота МОСТ). Парсер намеренно namespace-agnostic, поэтому
// после re-marshal корневой namespace может отличаться. Для
// приёмочных (receive-only) документов это несущественно — сравниваем
// без учёта XMLName.Space.
clearXMLNameSpace(s1)
clearXMLNameSpace(s2)
if !reflect.DeepEqual(s1, s2) {
t.Errorf("round-trip структуры разошлись:\nS1 = %+v\nS2 = %+v", s1, s2)
}