На отдельном мобильном стенде сохраните свой не секретный пакет счета, прочитайте обе строки, измените сумму и подтвердите восстановление до удаления только своего ключа.
Где и как запускать
Собственный новый Android ARM64 developer mobile-platform app; только МобильноеПриложениеКлиент и локальный МобильноеПриложениеСервер для scalar guard. Ни desktop, ни mobile-client fallback; действительная версия показана отдельно.
Подготовка
Будущий ручной опыт — только собственная новая изолированная лаборатория: отдельное Android ARM64 устройство, не личный телефон, и своя новая Windows-VM с подготовленным IIS. Здесь устройство, CF, публикация, установка и хранилище не запускались. Изучение кода само ничего не записывает.
Создайте с нуля собственную конфигурацию УчебныйСейф85 без прикладных данных. Версия конфигурации ровно 58dd39ee-7b27-40aa-9bcb-552ea90cf15c; режим совместимости Не использовать; назначение использования Приложение для мобильной платформы. Метка — учебная идентификация пакета, не криптографическое доказательство и не UUID ИБ/пользователя.
Общие → Общие формы: создайте ровно три управляемые формы ГлавнаяСейфа85, ПультСейфаАсинх85, ПультСейфаОповещения85. У главной формы создайте команды ОткрытьАсинх85 и ОткрытьОповещения85 с одноименными обработчиками; выведите две кнопки этих команд. Вставьте приведенный полный модуль главной формы. Поместите ГлавнаяСейфа85 в рабочую область начальной страницы конфигурации; это навигация, не автоматический запуск опыта.
У каждой формы пульта создайте реквизиты СогласенНаОпыт85 (Булево, исходно Ложь), ВерсияRuntime85/Состояние85/История85 (Строка неограниченной длины), Сумма85 (Число15,2). Выведите флажок согласия и поля наблюдения; поля наблюдения ТолькоПросмотр=Да. Создайте ровно свою команду ПройтиОпыт85, обработчик ПройтиОпыт85(Команда), кнопку этой команды. В свойствах события формы ПриОткрытии назначьте ПриОткрытии(Отказ); не вызывайте обработчики сами.
Полный MAIN модуль вставьте только в форму, названную в расположении соответствующей карточки. Другой пульт имеет другой полный MAIN модуль и отдельные внутренние переменные/согласие/ключ. Поддерживающий модуль ГлавнаяСейфа85 одинаков для двух карточек. Серверный helper находится внутри каждой формы, &НаСервереБезКонтекста: только МобильноеПриложениеСервер, возвращает Булево, opaque менеджер/данные/nonce через RPC не передаются.
Проверка конфигурации: Мобильное приложение — клиент и Мобильное приложение — сервер, целостность, ссылки и неподдерживаемые функции. При ошибках STOP. Путь поставки: Конфигурация → Мобильное приложение → Публиковать…; только собственный новый виртуальный каталог OwnSafePackets85_<ownGUID> и собственная папка своей VM/IIS, без автоматического обновления. Публикация создает каталог/мобильную конфигурацию и может предложить обновление только своей новой ИБ; не подключайте рабочую базу. Это не сборщик APK/IPA.
Для собственного Android ARM64 вручную используйте официальный пакет мобильной платформы разработчика 1cem-arm64.apk из своего фактически полученного мобильного дистрибутива. Не 1cem-client-arm64.apk и не 1cem-standalone-arm64.apk: это другие среды. Наличие Windows8.5.1.1343 не доказывает доступность/установку мобильного бинарного runtime8.5.1.1343. Здесь ничего не скачано и не установлено; неподходящий фактический пакет — STOP.
После самостоятельной установки developer-платформы в пустом списке приложений нажмите +, в Address задайте реальный URL своей private VM и точное имя своей публикации, нажмите Load, задайте имя УчебныйСейф85 и завершите создание (OK/Done), затем откройте свое приложение из списка. Не localhost телефона, не file://, не выдуманный готовый URL. Android должен видеть только свою VM в лабораторной локальной сети; публичный сервер/рабочая ИБ не нужны. Не включайте auto-restart, auto-update или debugging ради этого опыта. Данные desktop-ИБ не считаются перенесенными: счет создается самим кодом на устройстве.
Официальная инструкция поставки: https://1c-dn.com/library/publishing_of_1c_mobile_application/ ; вариант ARM64 и добавление приложения: https://1c-dn.com/library/tutorials/1c_enterprise_development_tools_user_guide/ ; собственная локальная мобильная ИБ и проверка конфигурации: https://1c-dn.com/library/tutorials/first_application_mobile_platform/ . Это документированный будущий путь, не подтверждение текущей установки.
На своем лабораторном устройстве заранее настройте блокировку экрана; PIN/пароль не выводите и не задавайте пароль по умолчанию. Native capability ScreenUnlockRequired проверяется до каждого этапа, False означает STOP без понижения до Нет. Четвертый обязательный аргумент PutAsync — СпособДополнительнойПроверкиПользователя.БиометрическаяИлиВводПароля; в выбранном режиме он игнорируется, это не обещание биометрического успеха.
Откройте только выбранный пульт. Поле ВерсияRuntime85 показывает реальную версию мобильного приложения; код требует формат8.x.x.x и минимум8.3.18 для Promise API, но это не runtime-тест конкретного устройства. Другой контекст, изменившаяся версия, ошибка API/guard/capability — STOP. Никакого запуска в desktop или замены безопасного хранилища массивом.
До нажатия прочитайте весь lifecycle. Каждая форма имеет отдельное согласие, изначально False; поставьте свой флажок и один раз нажмите ПройтиОпыт85. Код сразу потребляет согласие и создает один свежий UUID ключа в своей области AWAIT или CALLBACK. Записи будущего опыта: первая форма3, вторая1; каждая удаляет ровно свой1 ключ. Данные не секретные: OWN85, UUID, коды ROW-A/ROW-B и две числовые суммы. Посторонние ключи не перечисляются, не читаются и не удаляются.
При отмене, ошибке, чужом результате или неожидаемом этапе форма остается STOP/неизвестно. Нет автоматического retry/rollback/delete/cleanup. Не закрывайте и не открывайте пульт заново ради уборки: внутреннее состояние формы не обещано сохраняющимся после закрытия/перезапуска приложения. Сначала вручную выясните фактический исход только собственного ключа; инструкция не дает полномочий на чужое хранилище.
&НаКлиенте
Перем СогласиеИспользовано85, Остановка85, Неизвестно85, Этап85, Токен85,
Ключ85, Менеджер85, Способ85, ДопПроверка85, ОжидаемыйПакет85;
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Если СогласиеИспользовано85 <> Неопределено Или Остановка85 <> Неопределено Тогда
Отказ = Истина;
Возврат;
КонецЕсли;
СогласиеИспользовано85 = Ложь;
Остановка85 = Истина;
Неизвестно85 = Истина;
СогласенНаОпыт85 = Ложь;
Сумма85 = 0;
История85 = "";
Этап85 = "Подготовка";
Попытка
ВерсияRuntime85 = ПроверитьМобильнуюВерсию85();
Остановка85 = Ложь;
Неизвестно85 = Ложь;
Этап85 = "Готов";
Состояние85 = "Версия проверена. Прочитайте последствия и отдельно поставьте флажок согласия";
Исключение
Остановить85(ОписаниеОшибки());
Отказ = Истина;
КонецПопытки;
КонецПроцедуры
&НаКлиенте
Процедура Остановить85(Причина)
Остановка85 = Истина;
Неизвестно85 = Истина;
СогласенНаОпыт85 = Ложь;
Этап85 = "STOP";
Состояние85 = "STOP. Исход неизвестен; повтор и автоматическая уборка запрещены. " + Причина;
КонецПроцедуры
&НаКлиенте
Процедура ПодготовитьОпыт85(Команда, Префикс)
#Если МобильноеПриложениеКлиент Тогда
Если ТипЗнч(Команда) <> Тип("КомандаФормы") Или Команда <> Команды.Найти("ПройтиОпыт85")
Или СогласиеИспользовано85 <> Ложь Или Остановка85 <> Ложь Или Неизвестно85 <> Ложь
Или Этап85 <> "Готов" Или СогласенНаОпыт85 <> Истина Тогда
ВызватьИсключение "Нужна собственная команда и отдельное неиспользованное согласие; повтор запрещен";
КонецЕсли;
СогласиеИспользовано85 = Истина;
СогласенНаОпыт85 = Ложь;
Неизвестно85 = Истина;
Этап85 = "Подготовка";
Если Префикс <> "OWN_SAFE_PACKETS_85:AWAIT:" И Префикс <> "OWN_SAFE_PACKETS_85:CALLBACK:" Тогда
ВызватьИсключение "Разрешены только две собственные учебные области ключей";
КонецЕсли;
Токен85 = Новый УникальныйИдентификатор;
Ключ85 = Префикс + Строка(Токен85);
Менеджер85 = БезопасноеХранилище;
Способ85 = СпособЗащитыДоступаБезопасногоХранилища.ТребуетсяРазблокировкаЭкрана;
ДопПроверка85 = СпособДополнительнойПроверкиПользователя.БиометрическаяИлиВводПароля;
ПроверитьСостояние85("Подготовка");
ПроверитьЗанятость85(Ложь);
ОжидаемыйПакет85 = СоздатьПакет85(750, 250);
#Иначе
ВызватьИсключение "Нет мобильного клиентского контекста; хранилище не вызывается";
#КонецЕсли
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьСостояние85(ОжидаемыйЭтап)
#Если МобильноеПриложениеКлиент Тогда
Если Остановка85 <> Ложь Или СогласиеИспользовано85 <> Истина
Или СогласенНаОпыт85 <> Ложь Или Этап85 <> ОжидаемыйЭтап
Или ТипЗнч(Неизвестно85) <> Тип("Булево")
Или ТипЗнч(Токен85) <> Тип("УникальныйИдентификатор")
Или ТипЗнч(Ключ85) <> Тип("Строка")
Или (Ключ85 <> "OWN_SAFE_PACKETS_85:AWAIT:" + Строка(Токен85)
И Ключ85 <> "OWN_SAFE_PACKETS_85:CALLBACK:" + Строка(Токен85))
Или ТипЗнч(Менеджер85) <> Тип("МенеджерБезопасногоХранилища")
Или Способ85 <> СпособЗащитыДоступаБезопасногоХранилища.ТребуетсяРазблокировкаЭкрана
Или ДопПроверка85 <> СпособДополнительнойПроверкиПользователя.БиометрическаяИлиВводПароля
Или ПроверитьМобильнуюВерсию85() <> ВерсияRuntime85 Тогда
ВызватьИсключение "Изменились контекст, версия, собственный ключ или этап; STOP";
КонецЕсли;
Поддерживается = Менеджер85.ПоддерживаетсяЗащитаДоступа(Способ85, ДопПроверка85);
Если ТипЗнч(Поддерживается) <> Тип("Булево") Или Поддерживается <> Истина Тогда
ВызватьИсключение "ScreenUnlockRequired не поддержан; защита не понижается";
КонецЕсли;
#Иначе
ВызватьИсключение "Запрещен вызов мобильного хранилища из иной среды";
#КонецЕсли
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьЗанятость85(Ожидается)
#Если МобильноеПриложениеКлиент Тогда
Занят = Менеджер85.СодержитКлюч(Ключ85);
Если ТипЗнч(Занят) <> Тип("Булево") Или Занят <> Ожидается Тогда
ВызватьИсключение "Фактическая занятость собственного ключа не совпала";
КонецЕсли;
#Иначе
ВызватьИсключение "Проверка ключа допустима только в мобильном приложении";
#КонецЕсли
КонецПроцедуры
&НаКлиенте
Функция СоздатьПакет85(ПерваяСумма, ВтораяСумма)
Если ТипЗнч(ПерваяСумма) <> Тип("Число") Или ТипЗнч(ВтораяСумма) <> Тип("Число")
Или (ПерваяСумма <> 750 И ПерваяСумма <> 250) Или ВтораяСумма <> 250 Тогда
ВызватьИсключение "В этом конечном учебном пакете допустимы только 750/250 или 250/250";
КонецЕсли;
Возврат "OWN85|" + Строка(Токен85) + "|ROW-A|" + Формат(ПерваяСумма, "ЧГ=0;ЧДЦ=0")
+ "|ROW-B|" + Формат(ВтораяСумма, "ЧГ=0;ЧДЦ=0");
КонецФункции
&НаКлиенте
Функция ПроверитьПолныйПакет85(Данные, ПерваяСумма, ВтораяСумма)
Если Данные = Неопределено Тогда
ВызватьИсключение "Нет подтвержденных данных: возможна отмена; STOP";
КонецЕсли;
Если ТипЗнч(Данные) <> Тип("Строка") Тогда
ВызватьИсключение "Ожидалась настоящая строка собственного пакета";
КонецЕсли;
Если Данные <> ОжидаемыйПакет85 Или Данные <> СоздатьПакет85(ПерваяСумма, ВтораяСумма) Тогда
ВызватьИсключение "Не совпала вся фактически прочитанная строка";
КонецЕсли;
Части = СтрРазделить(Данные, "|", Истина);
Если Части.Количество() <> 6 Или Части[0] <> "OWN85"
Или Части[1] <> Строка(Токен85) Или Части[2] <> "ROW-A" Или Части[4] <> "ROW-B" Тогда
ВызватьИсключение "Не совпали число частей, nonce или оба кода строк";
КонецЕсли;
Первая = Число(Части[3]);
Вторая = Число(Части[5]);
Если ТипЗнч(Первая) <> Тип("Число") Или ТипЗнч(Вторая) <> Тип("Число")
Или Первая <> ПерваяСумма Или Вторая <> ВтораяСумма Тогда
ВызватьИсключение "Не совпали обе фактически разобранные числовые суммы";
КонецЕсли;
Возврат Первая + Вторая;
КонецФункции
&НаКлиенте
Процедура ПроверитьОтсутствиеОтмены85(Результат)
Если ТипЗнч(Результат) <> Тип("Булево") Или Результат <> Ложь Тогда
ВызватьИсключение "Отмена или неизвестный результат native операции; STOP";
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Функция ПроверитьМобильнуюВерсию85()
#Если МобильноеПриложениеКлиент Тогда
Информация = Новый СистемнаяИнформация;
Версия = Информация.ВерсияПриложения;
Если ПроверитьСвойПакетНаСервере85() <> Истина Тогда
ВызватьИсключение "Локальный сервер не подтвердил метку собственного пакета";
КонецЕсли;
Возврат Версия;
#Иначе
ВызватьИсключение "Этот урок запускается только в мобильном приложении, не в desktop или мобильном клиенте";
#КонецЕсли
КонецФункции
&НаКлиенте
Асинх Функция ПрочитатьОжидаемый85(ПерваяСумма, ВтораяСумма, НужныйЭтап)
#Если МобильноеПриложениеКлиент Тогда
ПроверитьСостояние85(НужныйЭтап);
ПроверитьЗанятость85(Истина);
Неизвестно85 = Истина;
Операция = Менеджер85.ПолучитьДанныеАсинх(Ключ85, "Прочитать только собственный учебный счет");
Если Операция = Неопределено Тогда
ВызватьИсключение "GetAsync сразу вернул Неопределено; продолжение запрещено";
КонецЕсли;
Если ТипЗнч(Операция) <> Тип("Обещание") Тогда
ВызватьИсключение "GetAsync не вернул обещание";
КонецЕсли;
Данные = Ждать Операция;
ПроверитьСостояние85(НужныйЭтап);
Если Неизвестно85 <> Истина Тогда
ВызватьИсключение "Ожидалось незавершенное собственное чтение";
КонецЕсли;
Сумма = ПроверитьПолныйПакет85(Данные, ПерваяСумма, ВтораяСумма);
ПроверитьЗанятость85(Истина);
Неизвестно85 = Ложь;
Возврат Сумма;
#Иначе
ВызватьИсключение "GetAsync не вызывается в иной среде";
#КонецЕсли
КонецФункции
&НаКлиенте
Асинх Процедура ПройтиОпыт85(Команда)
Попытка
#Если МобильноеПриложениеКлиент Тогда
ПодготовитьОпыт85(Команда, "OWN_SAFE_PACKETS_85:AWAIT:");
Этап85 = "НачальнаяЗапись";
ПроверитьСостояние85("НачальнаяЗапись");
Неизвестно85 = Истина;
Операция = Менеджер85.ПоместитьДанныеАсинх(Ключ85, ОжидаемыйПакет85, Способ85, ДопПроверка85);
Если ТипЗнч(Операция) <> Тип("Обещание") Тогда
ВызватьИсключение "PutAsync не вернул обещание";
КонецЕсли;
Результат = Ждать Операция;
ПроверитьСостояние85("НачальнаяЗапись");
ПроверитьОтсутствиеОтмены85(Результат);
Сумма85 = Ждать ПрочитатьОжидаемый85(750, 250, "НачальнаяЗапись");
Если Сумма85 <> 1000 Тогда ВызватьИсключение "Исходный счет не равен 1000"; КонецЕсли;
История85 = "1000";
Этап85 = "Замена";
Сумма85 = Ждать ПрочитатьОжидаемый85(750, 250, "Замена");
ПроверитьСостояние85("Замена");
НовыйПакет = СоздатьПакет85(250, 250);
Неизвестно85 = Истина;
Операция = Менеджер85.ПоместитьДанныеАсинх(Ключ85, НовыйПакет, Способ85, ДопПроверка85);
Если ТипЗнч(Операция) <> Тип("Обещание") Тогда
ВызватьИсключение "Replacement PutAsync не вернул обещание";
КонецЕсли;
Результат = Ждать Операция;
ПроверитьСостояние85("Замена");
ПроверитьОтсутствиеОтмены85(Результат);
ОжидаемыйПакет85 = НовыйПакет;
Сумма85 = Ждать ПрочитатьОжидаемый85(250, 250, "Замена");
Если Сумма85 <> 500 Тогда ВызватьИсключение "Измененный счет не равен 500"; КонецЕсли;
История85 = История85 + " → 500";
Этап85 = "Восстановление";
Сумма85 = Ждать ПрочитатьОжидаемый85(250, 250, "Восстановление");
ПроверитьСостояние85("Восстановление");
ИсходныйПакет = СоздатьПакет85(750, 250);
Неизвестно85 = Истина;
Операция = Менеджер85.ПоместитьДанныеАсинх(Ключ85, ИсходныйПакет, Способ85, ДопПроверка85);
Если ТипЗнч(Операция) <> Тип("Обещание") Тогда
ВызватьИсключение "Restoration PutAsync не вернул обещание";
КонецЕсли;
Результат = Ждать Операция;
ПроверитьСостояние85("Восстановление");
ПроверитьОтсутствиеОтмены85(Результат);
ОжидаемыйПакет85 = ИсходныйПакет;
Сумма85 = Ждать ПрочитатьОжидаемый85(750, 250, "Восстановление");
Если Сумма85 <> 1000 Тогда ВызватьИсключение "Восстановленный счет не равен 1000"; КонецЕсли;
История85 = История85 + " → 1000";
Этап85 = "Удаление";
Сумма85 = Ждать ПрочитатьОжидаемый85(750, 250, "Удаление");
ПроверитьСостояние85("Удаление");
Неизвестно85 = Истина;
Операция = Менеджер85.УдалитьДанныеАсинх(Ключ85);
Если ТипЗнч(Операция) <> Тип("Обещание") Тогда
ВызватьИсключение "DeleteAsync не вернул обещание";
КонецЕсли;
Результат = Ждать Операция;
ПроверитьСостояние85("Удаление");
ПроверитьОтсутствиеОтмены85(Результат);
ПроверитьЗанятость85(Ложь);
Неизвестно85 = Ложь;
Этап85 = "Завершен";
Состояние85 = "Подтверждено: 1000 → 500 → 1000; собственный точный ключ теперь отсутствует";
#Иначе
ВызватьИсключение "Мобильный опыт не запускается в другой среде";
#КонецЕсли
Исключение
Остановить85(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьСвойПакетНаСервере85()
#Если МобильноеПриложениеСервер Тогда
Если Метаданные.ОбщиеФормы.Количество() <> 3 Или Метаданные.ОбщиеФормы.Найти("ГлавнаяСейфа85") = Неопределено Или Метаданные.ОбщиеФормы.Найти("ПультСейфаАсинх85") = Неопределено Или Метаданные.ОбщиеФормы.Найти("ПультСейфаОповещения85") = Неопределено Тогда
ВызватьИсключение "Не совпадает собственный новый мобильный учебный пакет";
КонецЕсли;
Возврат Истина;
#Иначе
ВызватьИсключение "Нужен локальный сервер собственного мобильного приложения, не desktop ИБ";
#КонецЕсли
КонецФункции
Полный модуль собственной начальной формы: две настоящие команды без автоматического опыта
Своя CF → Общие → Общие формы → ГлавнаяСейфа85 → полный управляемый модуль
МобильноеПриложениеКлиент; серверный helper только МобильноеПриложениеСервер. Сначала своя метка и фактическая версия, затем ОткрытьФорму своей общей формы; записи/чтения сейфа не выполняются.
&НаКлиенте
Процедура ОткрытьАсинх85(Команда)
Если ТипЗнч(Команда) <> Тип("КомандаФормы") Или Команда <> Команды.Найти("ОткрытьАсинх85") Тогда
ВызватьИсключение "Нужна собственная команда главной формы";
КонецЕсли;
ПроверитьМобильнуюВерсию85();
ОткрытьФорму("ОбщаяФорма.ПультСейфаАсинх85");
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьОповещения85(Команда)
Если ТипЗнч(Команда) <> Тип("КомандаФормы") Или Команда <> Команды.Найти("ОткрытьОповещения85") Тогда
ВызватьИсключение "Нужна собственная команда главной формы оповещений";
КонецЕсли;
ПроверитьМобильнуюВерсию85();
ОткрытьФорму("ОбщаяФорма.ПультСейфаОповещения85");
КонецПроцедуры
&НаКлиенте
Функция ПроверитьМобильнуюВерсию85()
#Если МобильноеПриложениеКлиент Тогда
Информация = Новый СистемнаяИнформация;
Версия = Информация.ВерсияПриложения;
Если ПроверитьСвойПакетНаСервере85() <> Истина Тогда
ВызватьИсключение "Локальный сервер не подтвердил метку собственного пакета";
КонецЕсли;
Возврат Версия;
#Иначе
ВызватьИсключение "Этот урок запускается только в мобильном приложении, не в desktop или мобильном клиенте";
#КонецЕсли
КонецФункции
&НаСервереБезКонтекста
Функция ПроверитьСвойПакетНаСервере85()
#Если МобильноеПриложениеСервер Тогда
Если Метаданные.ОбщиеФормы.Количество() <> 3 Или Метаданные.ОбщиеФормы.Найти("ГлавнаяСейфа85") = Неопределено Или Метаданные.ОбщиеФормы.Найти("ПультСейфаАсинх85") = Неопределено Или Метаданные.ОбщиеФормы.Найти("ПультСейфаОповещения85") = Неопределено Тогда
ВызватьИсключение "Не совпадает собственный новый мобильный учебный пакет";
КонецЕсли;
Возврат Истина;
#Иначе
ВызватьИсключение "Нужен локальный сервер собственного мобильного приложения, не desktop ИБ";
#КонецЕсли
КонецФункции
Разбор по шагам
Откройте собственный пульт, прочитайте фактическую версию и последствия3 записей/1 удаления; отдельно согласитесь.
Из нового nonce создаются точный ключ и пакет750+250; capability должен быть True, ContainsKey своего ключаFalse.
Настоящий PutAsync имеет4 аргумента; BoolFalse после Ждать означает отсутствие отмены. GetAsync должен вернуть Promise, затем всю собственную строку и сумму1000.
Повторно прочитайте исходный целый пакет перед replacement. Запишите250+250 и настоящим новым чтением подтвердите500.
Повторно прочитайте измененный пакет перед restoration. Запишите750+250 и новым чтением подтвердите1000.
Еще раз прочитайте весь восстановленный пакет перед DeleteAsync; только после BoolFalse и фактического ContainsKeyFalse опыт завершен.
Как проверить результат
MAIN получает настоящий МенеджерБезопасногоХранилища через readonly global; объект не создается выдуманным конструктором.
После каждого Ждать/callback проверяются текущий этап, типы, версия, метка собственной CF и native capability; неизвестный исход не разрешает новую операцию.
Каждое фактическое чтение сравнивает всю строку,6 частей, свой UUID, оба кода и обе преобразованные числовые суммы.
Before replacement/restoration/deletion источник прочитан заново; чужой либо изменившийся пакет вызывает STOP.
Каждый опыт имеет собственный consumed-consent и свой новый nonce-ключ. Никаких чужих ключей, ручных событий и автоматического восстановления.
Ожидаемый результат
Ожидаемый ручной результат: История85 = 1000 → 500 → 1000, Сумма85=1000; только созданный этим пультом ключ отсутствует. Runtime здесь не проверен.
На что обратить внимание
BoolFalse у Put/DeleteAsync — признак отсутствия отмены, не доказательство сохранения/удаления: нужны реальные контрольные чтения.
GetAsync документирует Promise или прямое Неопределено; неопределенные конечные данные тоже STOP, поскольку свой producer никогда не сохраняет Неопределено.
ContainsKey означает только занятость конкретного ключа и не выполняет пользовательскую проверку. Он не доказывает пустоту всего сейфа.
BeginPut исключен: установленная справка противоречит сама себе в типе четвертого аргумента. Не угадываем другой enum или отсутствующий обязательный параметр.
Sticky STOP и consumed-consent относятся к текущему экземпляру формы; нет обещания сохранения состояния после закрытия/переустановки.
Чтение с последующей записью не является атомарным CAS или доказательством отсутствия гонки; используйте только свой отдельный стенд и единственного оператора.
Будущий ручной код действительно использует защищенное хранилище, ОС I/O и возможные системные проверки. Здесь не было runtime и криптографического теста.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.