Исправлено тире

This commit is contained in:
2026-05-30 16:47:52 +03:00
parent af9cd2dead
commit 229c0629f8
27 changed files with 10430 additions and 10380 deletions

View File

@@ -2,7 +2,7 @@
== Обзор программного продукта Wallenc
Wallenc мобильное приложение для Android: `VaultsManager` объединяет vault (один локальный и удалённые по OAuth), внутри каждого vault пользователь управляет storage с файлами и клиентским шифрованием. Продукт не использует собственный сервер; взаимодействие с облаком выполняется через API внешних провайдеров после OAuth.
Wallenc мобильное приложение для Android: `VaultsManager` объединяет vault (один локальный и удалённые по OAuth), внутри каждого vault пользователь управляет storage с файлами и клиентским шифрованием. Продукт не использует собственный сервер; взаимодействие с облаком выполняется через API внешних провайдеров после OAuth.
== Техническое задание
@@ -13,11 +13,11 @@ Wallenc — мобильное приложение для Android: `VaultsManag
== Программа и методика испытаний
Испытания проводятся на устройстве или эмуляторе Android. Матрица сценариев табл. @tbl-testplan. Критерий приёмки: отсутствие блокирующих дефектов по сценариям T-1T-6.
Испытания проводятся на устройстве или эмуляторе Android. Матрица сценариев табл. @tbl-testplan. Критерий приёмки: отсутствие блокирующих дефектов по сценариям T-1T-6.
== Отчёт о результатах испытаний
Результаты приведены в табл. @tbl-testres (глава 5). Модульные тесты криптографии пройдены.
Результаты приведены в табл. @tbl-testres (глава 5). Модульные тесты криптографии пройдены.
== Руководство пользователя

View File

@@ -10,16 +10,16 @@
[Обозначения и сокращения],
2,
table.header([Обозначение], [Расшифровка]),
[API], [Application Programming Interface программный интерфейс приложения],
[DAO], [Data Access Object объект доступа к данным],
[E2E], [End-to-end сквозное шифрование],
[MVVM], [Model View ViewModel],
[API], [Application Programming Interface программный интерфейс приложения],
[DAO], [Data Access Object объект доступа к данным],
[E2E], [End-to-end сквозное шифрование],
[MVVM], [Model View ViewModel],
[OAuth], [протокол авторизации открытого типа],
[UI], [User Interface пользовательский интерфейс],
[CRUD], [Create, Read, Update, Delete создание, чтение, изменение, удаление],
[UI], [User Interface пользовательский интерфейс],
[CRUD], [Create, Read, Update, Delete создание, чтение, изменение, удаление],
[ПЗ], [Пояснительная записка],
[ТЗ], [Техническое задание],
[ВКР], [Выпускная квалификационная работа],
[JUnit], [Фреймворк модульного тестирования Java/Kotlin],
[IT], [Instrumented Test инструментальный тест Android],
[IT], [Instrumented Test инструментальный тест Android],
) <tbl-abbr>

View File

@@ -16,5 +16,5 @@
parbreak()
[Реализованы иерархия VaultsManager vault storage файлы (один LocalVault на устройстве, удалённые vault по OAuth), клиентское AES-шифрование, служебные метаданные в Room и проектный контур синхронизации без передачи ключей провайдеру. Исходный код размещён в приватном репозитории GitLab ЮФУ. Полный листинг исходных файлов приложение А; программная документация приложение Б.]
[Реализованы иерархия VaultsManager vault storage файлы (один LocalVault на устройстве, удалённые vault по OAuth), клиентское AES-шифрование, служебные метаданные в Room и проектный контур синхронизации без передачи ключей провайдеру. Исходный код размещён в приватном репозитории GitLab ЮФУ. Полный листинг исходных файлов приложение А; программная документация приложение Б.]
}

View File

@@ -18,7 +18,7 @@
=== Назначение и цели создания системы
*Назначение* системы Wallenc предоставление пользователю мобильного клиента для работы с иерархией *VaultsManager → vault → storage → файлы*: один `LocalVault` на устройстве, удалённые vault по OAuth; внутри vault пользователь создаёт и управляет *storage*; шифрование (`StorageEncryptionInfo`, `Encryptor`) применяется к storage, а не к vault.
*Назначение* системы Wallenc предоставление пользователю мобильного клиента для работы с иерархией *VaultsManager → vault → storage → файлы*: один `LocalVault` на устройстве, удалённые vault по OAuth; внутри vault пользователь создаёт и управляет *storage*; шифрование (`StorageEncryptionInfo`, `Encryptor`) применяется к storage, а не к vault.
*Цели создания*: обеспечить единую модель vault и storage; минимизировать доверие к провайдеру; поддержать расширение списка провайдеров через адаптеры; сохранить служебные метаданные в локальной БД без хранения пользовательского контента в открытом виде.
@@ -30,7 +30,7 @@
[Свод функциональных требований],
2,
table.header([Код], [Требование]),
[ФР-1], [Создание, просмотр, переименование и удаление storage в локальном vault (LocalVault один на устройстве)],
[ФР-1], [Создание, просмотр, переименование и удаление storage в локальном vault (LocalVault один на устройстве)],
[ФР-2], [Включение шифрования storage, проверка ключа, открытие и закрытие зашифрованного представления],
[ФР-3], [Просмотр и операции с файлами внутри storage; текстовые секреты и 2FA],
[ФР-4], [OAuth-авторизация (Яндекс), регистрация удалённых vault и листинг их storage],
@@ -66,7 +66,7 @@
=== Требования к программно-аппаратной платформе
Минимальная платформа Android с поддержкой Jetpack Compose; для OAuth и удалённых операций требуется сетевое подключение. Объём оперативной памяти должен быть достаточен для фоновых задач шифрования и Room.
Минимальная платформа Android с поддержкой Jetpack Compose; для OAuth и удалённых операций требуется сетевое подключение. Объём оперативной памяти должен быть достаточен для фоновых задач шифрования и Room.
== Аналоги
@@ -92,7 +92,7 @@ Google Files Secure Folder локально прячет и защищает ф
[Сравнительная оценка аналогов],
6,
table.header([Критерий], [Secure Folder], [Proton], [Bitwarden], [Cryptomator], [Wallenc]),
[Собственный backend приложения], [], [+], [+/], [], [],
[Собственный backend приложения], [], [+], [+/], [], [],
[E2E / клиентское шифрование], [+/], [+], [+], [+], [+],
[Файловый vault], [+], [+], [], [+], [+],
[OAuth внешнего провайдера], [], [+/], [+/], [+/], [+],
@@ -122,10 +122,10 @@ Google Files Secure Folder локально прячет и защищает ф
== Обзор рынка и обоснование выбора решения Wallenc
Рынок мобильных хранилищ демонстрирует поляризацию: закрытые экосистемы с удобным UX (Proton, Google) и открытые криптоклиенты с ручной настройкой (Cryptomator). Wallenc занимает промежуточную нишу *мобильный zero-knowledge vault* с OAuth к популярному провайдеру (Яндекс) и расширяемой регистрацией типов хранилищ. Для корпоративного заказчика (Нейротех) важны: отсутствие затрат на сервер приложения, возможность аудита кода, формализованное тестирование (68 unit-тестов, см. гл. 5).
Рынок мобильных хранилищ демонстрирует поляризацию: закрытые экосистемы с удобным UX (Proton, Google) и открытые криптоклиенты с ручной настройкой (Cryptomator). Wallenc занимает промежуточную нишу *мобильный zero-knowledge vault* с OAuth к популярному провайдеру (Яндекс) и расширяемой регистрацией типов хранилищ. Для корпоративного заказчика (Нейротех) важны: отсутствие затрат на сервер приложения, возможность аудита кода, формализованное тестирование (68 unit-тестов, см. гл. 5).
Перспективы коммерциализации связаны не с продажей облака, а с лицензированием клиента или внутренним внедрением. Барьер входа ответственность пользователя за ключ; в ПЗ это отражено в руководстве пользователя (прил. Б) и предупреждениях в UI.
Перспективы коммерциализации связаны не с продажей облака, а с лицензированием клиента или внутренним внедрением. Барьер входа ответственность пользователя за ключ; в ПЗ это отражено в руководстве пользователя (прил. Б) и предупреждениях в UI.
== Формирование технического задания
Структура ТЗ оформлена по ГОСТ 7.322017: цели, этапы, функциональные и нефункциональные требования, порядок приёмки. Приоритет отдан ядру хранения и шифрования; расширения (2FA, текстовые секреты, синхронизация) зафиксированы как дополнительные функции второго этапа практики. Полный текст ТЗ в приложении Б.
Структура ТЗ оформлена по ГОСТ 7.322017: цели, этапы, функциональные и нефункциональные требования, порядок приёмки. Приоритет отдан ядру хранения и шифрования; расширения (2FA, текстовые секреты, синхронизация) зафиксированы как дополнительные функции второго этапа практики. Полный текст ТЗ в приложении Б.

View File

@@ -10,13 +10,13 @@
=== Карта процессов
Основные процессы: создание storage в vault опциональное шифрование storage открытие storage работа с файлами закрытие; для удалённых vault OAuth привязка учётной записи листинг storage провайдера проектная синхронизация.
Основные процессы: создание storage в vault опциональное шифрование storage открытие storage работа с файлами закрытие; для удалённых vault OAuth привязка учётной записи листинг storage провайдера проектная синхронизация.
=== Диаграмма BPMN
На рисунке @fig-15 представлена диаграмма BPMN основного процесса работы со storage внутри vault.
#pz-fig("fig_15_bpmn_vault.png", [BPMN: storage создание, шифрование, открытие, содержимое], "fig-15")
#pz-fig("fig_15_bpmn_vault.png", [BPMN: storage создание, шифрование, открытие, содержимое], "fig-15")
=== Карта систем
@@ -46,9 +46,9 @@ DFD уровня 0 (рис. @fig-16) отражает потоки между UI
Модель данных приложения строится на трёх уровнях (см. `IVaultsManager`, `IVault`, `IStorage` на рис. @fig-04):
+ *VaultsManager* единая точка доступа: реактивный список всех vault (`vaults`), агрегированный список storage (`allStorages`) и `IUnlockManager` для открытых storage;
+ *IVault* контейнер, объединяющий набор storage. *LocalVault* в приложении один (корень на устройстве); удалённые vault (например, `YandexVault`) добавляются по одному на привязанную учётную запись OAuth;
+ *IStorage* хранилище, которое пользователь создаёт, переименовывает и удаляет в интерфейсе; внутри файлы и каталоги, доступные через `IStorageAccessor` (с опциональным клиентским шифрованием).
- *VaultsManager* единая точка доступа: реактивный список всех vault (`vaults`), агрегированный список storage (`allStorages`) и `IUnlockManager` для открытых storage;
- *IVault* контейнер, объединяющий набор storage. *LocalVault* в приложении один (корень на устройстве); удалённые vault (например, `YandexVault`) добавляются по одному на привязанную учётную запись OAuth;
- *IStorage* хранилище, которое пользователь создаёт, переименовывает и удаляет в интерфейсе; внутри файлы и каталоги, доступные через `IStorageAccessor` (с опциональным клиентским шифрованием).
Пользовательские сценарии «создать локальный vault» в UI соответствуют созданию нового *storage* внутри единственного `LocalVault`, а не появлению второго локального vault.
@@ -62,13 +62,13 @@ DFD уровня 0 (рис. @fig-16) отражает потоки между UI
#pz-fig("fig_18_deployment.png", [Диаграмма развёртывания], "fig-18")
Архитектурные слои MVVM + Clean Architecture и соответствие модулям Gradle на рисунке @fig-19.
Архитектурные слои MVVM + Clean Architecture и соответствие модулям Gradle на рисунке @fig-19.
#pz-fig("fig_19_clean_architecture.png", [Слои Clean Architecture и модули проекта], "fig-19")
== Проектирование локальной базы данных
Служебная БД Room хранит ключи шифрования для пар «исходный storage зашифрованное представление» (`DbStorageKeyMap`), сериализованные метаданные storage (`DbStorageMetaInfo`), учётные записи Яндекс (`DbYandexAccount`) и группы синхронизации (`DbStorageSyncGroup`). Пользовательский контент в открытом виде в БД не сохраняется только параметры, необходимые для восстановления состояния приложения при следующем запуске. Доступ инкапсулирован в DAO и репозиториях модуля `:infrastructure-android`.
Служебная БД Room хранит ключи шифрования для пар «исходный storage зашифрованное представление» (`DbStorageKeyMap`), сериализованные метаданные storage (`DbStorageMetaInfo`), учётные записи Яндекс (`DbYandexAccount`) и группы синхронизации (`DbStorageSyncGroup`). Пользовательский контент в открытом виде в БД не сохраняется только параметры, необходимые для восстановления состояния приложения при следующем запуске. Доступ инкапсулирован в DAO и репозиториях модуля `:infrastructure-android`.
#pz-table(
[Сущности Room и назначение],
@@ -82,7 +82,7 @@ DFD уровня 0 (рис. @fig-16) отражает потоки между UI
== Проектирование подсистемы синхронизации
Подсистема синхронизации спроектирована как набор независимых операций над журналом изменений каждого `Storage`. Алгоритм выбирает «победителя» по ревизии, копирует или удаляет файлы на целевом хранилище, не расшифровывая данные на сервере. Блокировки предотвращают одновременную синхронизацию одной группы; при отмене задачи блокировки снимаются (покрыто unit-тестами `syncGroupCooperativeCancellationReleasesLocks` и др., гл. 5). Подробное описание реализации и выбора источника для каждого пути в гл. 4 алгоритм согласования журналов, рис. @fig-35).
Подсистема синхронизации спроектирована как набор независимых операций над журналом изменений каждого `Storage`. Алгоритм выбирает «победителя» по ревизии, копирует или удаляет файлы на целевом хранилище, не расшифровывая данные на сервере. Блокировки предотвращают одновременную синхронизацию одной группы; при отмене задачи блокировки снимаются (покрыто unit-тестами `syncGroupCooperativeCancellationReleasesLocks` и др., гл. 5). Подробное описание реализации и выбора источника для каждого пути в гл. 4 алгоритм согласования журналов, рис. @fig-35).
== Нефункциональные архитектурные решения

View File

@@ -54,7 +54,7 @@ CJM сценария «защитить и открыть storage» предст
== Проработка прототипа и особенности дизайна
Интерфейс реализован на Jetpack Compose @compose-docs. Экраны локальных и удалённых vault, диалоги шифрования, OAuth, а также разделы текстовых секретов и 2FA внутри storage показаны на рис. @fig-05@fig-10 и @fig-33@fig-34 (подробно приложение В и руководство пользователя в приложении Б).
Интерфейс реализован на Jetpack Compose @compose-docs. Экраны локальных и удалённых vault, диалоги шифрования, OAuth, а также разделы текстовых секретов и 2FA внутри storage показаны на рис. @fig-05@fig-10 и @fig-33@fig-34 (подробно приложение В и руководство пользователя в приложении Б).
#pz-fig("fig_05_local_vaults.jpg", [Список storage в локальном vault (экран «локальные vault»)], "fig-05")
#pz-fig("fig_06_encrypt_dialog.jpg", [Диалог включения шифрования], "fig-06")

View File

@@ -2,7 +2,7 @@
=== Модуль :vault-contracts
Определяет дескрипторы vault (`VaultDescriptor`, `DescribedVault`) и контракты регистрации удалённых vault (`VaultRegistrar`, `VaultRegistration`). Реализация агрегатора `VaultsManager` в `:domain-vault`.
Определяет дескрипторы vault (`VaultDescriptor`, `DescribedVault`) и контракты регистрации удалённых vault (`VaultRegistrar`, `VaultRegistration`). Реализация агрегатора `VaultsManager` в `:domain-vault`.
=== Модуль :domain-vault
@@ -14,7 +14,7 @@
=== Модуль :infrastructure-android
Реализует Room (`AppDb` v5), DAO, репозитории, OAuth-хранилище токенов, файловые адаптеры Android. Модуль единственная точка зависимости от Android SDK в слое данных.
Реализует Room (`AppDb` v5), DAO, репозитории, OAuth-хранилище токенов, файловые адаптеры Android. Модуль единственная точка зависимости от Android SDK в слое данных.
=== Сборка и зависимости

View File

@@ -11,7 +11,7 @@
=== Модуль :domain-vault
Инкапсулирует сетевой доступ к Yandex Disk и преобразование исключений Retrofit/OkHttp в доменные коды. Тесты репозитория не обращаются к реальной сети подставляется фейковый HTTP-слой, что обеспечивает детерминированность CI.
Инкапсулирует сетевой доступ к Yandex Disk и преобразование исключений Retrofit/OkHttp в доменные коды. Тесты репозитория не обращаются к реальной сети подставляется фейковый HTTP-слой, что обеспечивает детерминированность CI.
=== Модуль :ui
@@ -19,7 +19,7 @@
=== Модуль :infrastructure-android
Связывает Room, DataStore, OAuth Activity Result API, реализации репозиториев. Версия схемы БД 5; миграции отключены (`exportSchema = false`) на этапе прототипа. Инструментальный `YandexAccountRepositoryTest` подтверждает CRUD учётной записи в in-memory БД.
Связывает Room, DataStore, OAuth Activity Result API, реализации репозиториев. Версия схемы БД 5; миграции отключены (`exportSchema = false`) на этапе прототипа. Инструментальный `YandexAccountRepositoryTest` подтверждает CRUD учётной записи в in-memory БД.
=== Модуль :task-runtime
@@ -27,7 +27,7 @@
=== Модуль :vault-contracts и :app
`:vault-contracts` задаёт точку расширения для новых провайдеров. `:app` Hilt-модули, `Application`, навигационный граф, сборка APK. Точка входа не содержит бизнес-правил; они делегируются use case.
`:vault-contracts` задаёт точку расширения для новых провайдеров. `:app` Hilt-модули, `Application`, навигационный граф, сборка APK. Точка входа не содержит бизнес-правил; они делегируются use case.
=== Журнал разработки и контроль версий

View File

@@ -32,19 +32,19 @@ fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boole
}
```
Блок-схема сценария рис. @fig-21.
Блок-схема сценария рис. @fig-21.
#pz-fig("fig_21_encrypt_flow.png", [Блок-схема: enableEncryption checkKey openStorage], "fig-21")
=== Модуль адаптеров хранилищ
`VaultsManager` агрегирует один `LocalVault` и удалённые vault; адаптеры реализуют доступ к файлам внутри каждого `IStorage`. Регистрация удалённых vault через модуль `:vault-contracts`.
`VaultsManager` агрегирует один `LocalVault` и удалённые vault; адаптеры реализуют доступ к файлам внутри каждого `IStorage`. Регистрация удалённых vault через модуль `:vault-contracts`.
=== Модуль синхронизации хранилищ
В Room хранится сущность `DbStorageSyncGroup` набор UUID `Storage`, которые должны иметь согласованное состояние. Запуск синхронизации выполняется через `RunStorageSyncUseCase` / `WorkManager` и debounce при изменении файлов (рис. @fig-01@fig-03). Движок `StorageSyncEngine` (модуль `:usecases`) реализует согласование журналов изменений; доработка касается в основном политики фонового расписания и UX отображения прогресса.
В Room хранится сущность `DbStorageSyncGroup` набор UUID `Storage`, которые должны иметь согласованное состояние. Запуск синхронизации выполняется через `RunStorageSyncUseCase` / `WorkManager` и debounce при изменении файлов (рис. @fig-01@fig-03). Движок `StorageSyncEngine` (модуль `:usecases`) реализует согласование журналов изменений; доработка касается в основном политики фонового расписания и UX отображения прогресса.
Для каждого `Storage` ведётся журнал изменений по относительным путям пользовательских файлов. Запись журнала (`StorageSyncJournalEntry`) содержит операцию (`UPSERT`, `TRASH`, `DELETE`) и ревизию (`sequence`, `actorId`, `createdAt`). Ключи шифрования в обмен не включаются провайдер видит только зашифрованные объекты. Модуль `:task-runtime` обслуживает длительные задачи шифрования и синхронизации без блокировки UI.
Для каждого `Storage` ведётся журнал изменений по относительным путям пользовательских файлов. Запись журнала (`StorageSyncJournalEntry`) содержит операцию (`UPSERT`, `TRASH`, `DELETE`) и ревизию (`sequence`, `actorId`, `createdAt`). Ключи шифрования в обмен не включаются провайдер видит только зашифрованные объекты. Модуль `:task-runtime` обслуживает длительные задачи шифрования и синхронизации без блокировки UI.
=== Алгоритм согласования журналов синхронизации
@@ -52,18 +52,18 @@ fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boole
#pz-fig("fig_35_sync_merge_algorithm.png", [Алгоритм согласования журналов (StorageSyncEngine)], "fig-35")
*Подготовка.* По UUID из `DbStorageSyncGroup` загружаются объекты `IStorage`. Если в группе меньше двух хранилищ или несовместимы параметры шифрования, синхронизация пропускается. На каждом accessor запрашивается блокировка sync (lease на удалённом диске best-effort; внутри процесса группа сериализуется `Mutex`). Параллельно для каждого storage вызываются `flushPendingSyncJournal()` и `readSyncJournal()`; служебные пути отфильтровываются (`StorageSyncPaths.isSyncableUserPath`).
*Подготовка.* По UUID из `DbStorageSyncGroup` загружаются объекты `IStorage`. Если в группе меньше двух хранилищ или несовместимы параметры шифрования, синхронизация пропускается. На каждом accessor запрашивается блокировка sync (lease на удалённом диске best-effort; внутри процесса группа сериализуется `Mutex`). Параллельно для каждого storage вызываются `flushPendingSyncJournal()` и `readSyncJournal()`; служебные пути отфильтровываются (`StorageSyncPaths.isSyncableUserPath`).
*Слияние журналов.* Объект `StorageSyncJournalMerge` объединяет журналы в отображение «путь победитель». Для каждого пути остаётся запись с наибольшей ревизией. Сравнение реализовано функцией `compareEntries`: сначала `revision.sequence`, при равенстве `actorId`, затем `createdAt`. Такой порядок обеспечивает детерминированный выбор одной записи при конкурентных изменениях на разных устройствах.
*Слияние журналов.* Объект `StorageSyncJournalMerge` объединяет журналы в отображение «путь победитель». Для каждого пути остаётся запись с наибольшей ревизией. Сравнение реализовано функцией `compareEntries`: сначала `revision.sequence`, при равенстве `actorId`, затем `createdAt`. Такой порядок обеспечивает детерминированный выбор одной записи при конкурентных изменениях на разных устройствах.
*Выбор источника для пути.* После слияния для каждой пары (путь, `winnerEntry`) вызывается `findSourceStorage`:
+ при `UPSERT` первый `Storage`, у которого запись по этому пути *совпадает* с победителем (`compareEntries == 0`): оттуда читаются байты для копирования;
+ при `DELETE` или `TRASH` первый `Storage`, где путь ещё присутствует в журнале (или любой storage группы, если записей нет): оттуда инициируется удаление на целях.
- при `UPSERT` первый `Storage`, у которого запись по этому пути *совпадает* с победителем (`compareEntries == 0`): оттуда читаются байты для копирования;
- при `DELETE` или `TRASH` первый `Storage`, где путь ещё присутствует в журнале (или любой storage группы, если записей нет): оттуда инициируется удаление на целях.
Если для `UPSERT` источник не найден, путь пропускается (файл уже отсутствует на всех носителях).
*Распространение на цели.* Для каждого `target` в группе, отличного от источника, сравнивается ревизия записи на цели с `winnerEntry`. Если цель уже не слабее победителя (`compareEntries(target, winner) >= 0`), шаг пропускается. Иначе вызывается `applyEntry`: для `UPSERT` потоковое копирование `openRead` `openWrite`; для `DELETE` / `TRASH` `delete` или `moveToTrash`. Все операции выполняются с `recordSyncJournal = false`, чтобы не порождать цикл повторной синхронизации. Ошибки отдельных путей учитываются счётчиком `applyFailures`, отмена кооперативная (проверка `syncGeneration`, снятие блокировок в `finally`).
*Распространение на цели.* Для каждого `target` в группе, отличного от источника, сравнивается ревизия записи на цели с `winnerEntry`. Если цель уже не слабее победителя (`compareEntries(target, winner) >= 0`), шаг пропускается. Иначе вызывается `applyEntry`: для `UPSERT` потоковое копирование `openRead` `openWrite`; для `DELETE` / `TRASH` `delete` или `moveToTrash`. Все операции выполняются с `recordSyncJournal = false`, чтобы не порождать цикл повторной синхронизации. Ошибки отдельных путей учитываются счётчиком `applyFailures`, отмена кооперативная (проверка `syncGeneration`, снятие блокировок в `finally`).
Фрагмент сравнения ревизий и выбора источника:
@@ -154,7 +154,7 @@ remoteVaultAuthenticator.beginLink(CloudBrand.YANDEX) { outcome ->
#pz-fig("fig_23_module_deps.png", [Зависимости модулей Gradle], "fig-23")
В основном тексте приведены показательные фрагменты; полные листинги в приложении А.
В основном тексте приведены показательные фрагменты; полные листинги в приложении А.
#include "ch04-expand.typ"
#include "ch04-modules.typ"

View File

@@ -8,7 +8,7 @@
== Модульные тесты (JUnit)
В проекте реализовано 68 автоматических unit-тестов в пяти модулях (`:domain` 12, `:domain-vault` 10, `:usecases` 25, `:ui` 15, `:task-runtime` 6). Тесты выполняются на JVM при сборке.
В проекте реализовано 68 автоматических unit-тестов в пяти модулях (`:domain` 12, `:domain-vault` 10, `:usecases` 25, `:ui` 15, `:task-runtime` 6). Тесты выполняются на JVM при сборке.
#include "ch05-tests-generated.typ"
@@ -16,13 +16,13 @@
Класс `EncryptorTest` проверяет сценарии AES: `checkKey`, шифрование строк, байтовых массивов и потоков с верным и неверным ключом (строки 514 табл. @tbl-unit-all). `WallencExceptionMappingTest` покрывает преобразование файловых и сетевых исключений.
Прогон `./gradlew :domain:test` на рис. @fig-27.
Прогон `./gradlew :domain:test` на рис. @fig-27.
#pz-fig("fig_27_gradle_domain_test.png", [Отчёт Gradle: модуль :domain, задача test], "fig-27")
=== Синхронизация, 2FA и use cases
`StorageSyncEngineTest` моделирует группы синхронизации, копирование и удаление файлов, soft-delete, отмену и блокировки (строки 5264 табл. @tbl-unit-all); отдельно проверяются слияние журнала (`mergeKeepsSingleEntryPerPath`) и пропуск цели с актуальной ревизией (`syncSkipsWhenTargetRevisionAlreadyWinner`) см. алгоритм в гл. 4. `TwoFaTotpTest` сверяет TOTP с эталоном Java OTP. `StorageDomainUseCasesTest` проверяет CRUD текстовых секретов и 2FA.
`StorageSyncEngineTest` моделирует группы синхронизации, копирование и удаление файлов, soft-delete, отмену и блокировки (строки 5264 табл. @tbl-unit-all); отдельно проверяются слияние журнала (`mergeKeepsSingleEntryPerPath`) и пропуск цели с актуальной ревизией (`syncSkipsWhenTargetRevisionAlreadyWinner`) см. алгоритм в гл. 4. `TwoFaTotpTest` сверяет TOTP с эталоном Java OTP. `StorageDomainUseCasesTest` проверяет CRUD текстовых секретов и 2FA.
#pz-fig("fig_28_gradle_usecases_test.png", [Отчёт Gradle: модуль :usecases], "fig-28")
@@ -52,7 +52,7 @@
[:app], [YandexDiskLiveIntegrationTest], [Живой API (при наличии токена)], [3],
) <tbl-androidtest>
Запуск: `./gradlew connectedDebugAndroidTest`. Результат рис. @fig-31. Отрисовка экранов секретов и 2FA подтверждена скриншотами @fig-33@fig-34.
Запуск: `./gradlew connectedDebugAndroidTest`. Результат рис. @fig-31. Отрисовка экранов секретов и 2FA подтверждена скриншотами @fig-33@fig-34.
#pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest], "fig-31")
@@ -76,7 +76,7 @@
== Отчёт о результатах тестирования
По итогам `./gradlew test` все 68 unit-тестов завершились со статусом PASSED. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room персистентность учётной записи Яндекс.
По итогам `./gradlew test` все 68 unit-тестов завершились со статусом PASSED. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room персистентность учётной записи Яндекс.
#pz-fig("fig_30_gradle_test_summary.png", [Сводка Gradle test по модулям], "fig-30")

View File

@@ -4,7 +4,7 @@
== Обзор рынка программного обеспечения и аналогов
Рынок клиентских решений для защищённого хранения данных представлен продуктами классов «локальный сейф», менеджеры секретов и zero-knowledge файловые клиенты (см. табл. @tbl-analog, гл. 1). Ниша Wallenc мобильный универсальный vault без собственного backend приложения с возможностью подключения внешних провайдеров через OAuth.
Рынок клиентских решений для защищённого хранения данных представлен продуктами классов «локальный сейф», менеджеры секретов и zero-knowledge файловые клиенты (см. табл. @tbl-analog, гл. 1). Ниша Wallenc мобильный универсальный vault без собственного backend приложения с возможностью подключения внешних провайдеров через OAuth.
== Оценка экономических затрат на разработку проекта
@@ -31,11 +31,11 @@
[06.05.2026], [Защита практики, итоговая сборка ПЗ],
) <tbl-calendar>
Календарный график работ совпадает с дневником практики: этап 1 аналитика и проектирование (февраль–март 2026); этап 2 реализация (29.0319.04.2026); оформление документации апрель–май 2026.
Календарный график работ совпадает с дневником практики: этап 1 аналитика и проектирование (февраль–март 2026); этап 2 реализация (29.0319.04.2026); оформление документации апрель–май 2026.
== Модель применения и окупаемости
*Сценарий применения* внедрение в ООО НМФ «Нейротех» как внутренний инструмент защищённого хранения файлов сотрудников на корпоративных или личных облачных аккаунтах при сохранении zero-knowledge модели.
*Сценарий применения* внедрение в ООО НМФ «Нейротех» как внутренний инструмент защищённого хранения файлов сотрудников на корпоративных или личных облачных аккаунтах при сохранении zero-knowledge модели.
*Эффект*: снижение риска утечки при компрометации провайдера; отсутствие затрат на собственный сервер приложения. Окупаемость для учебно-промышленного прототипа выражается не в прямой прибыли, а в сокращении рисков и возможности доработки продукта без смены архитектуры.

View File

@@ -10,7 +10,7 @@
== Связь компетенции с предметом ВКР
Wallenc уже оперирует структурированными данными, пригодными для анализа: журнал синхронизации (пути, операции, ревизии, метки времени), метаданные файлов (имя, размер, тип по расширению), события фоновых задач. Это не «сырой» пользовательский контент, а агрегаты, которые можно обрабатывать локально. Компетенция ВПК-2 в контексте ВКР раскрывается постановкой задач, выбором данных и моделей, метриками качества и правилами принятия решений даже если в MVP остаётся только проектный анализ.
Wallenc уже оперирует структурированными данными, пригодными для анализа: журнал синхронизации (пути, операции, ревизии, метки времени), метаданные файлов (имя, размер, тип по расширению), события фоновых задач. Это не «сырой» пользовательский контент, а агрегаты, которые можно обрабатывать локально. Компетенция ВПК-2 в контексте ВКР раскрывается постановкой задач, выбором данных и моделей, метриками качества и правилами принятия решений даже если в MVP остаётся только проектный анализ.
== Прикладные задачи анализа данных и принимаемые решения
@@ -34,12 +34,12 @@ Wallenc уже оперирует структурированными данн
*OCR.* Google ML Kit Text Recognition @mlkit-text позволяет извлечь текст без собственного обучения; дообучение нужно только для специфичных шрифтов. Решение: предзаполнить поле секрета с возможностью редактирования.
Общая схема on-device inference приведена на рис. @fig-36: метаданные и признаки не покидают устройство в открытом виде; обучение на расшифрованном содержимом всех пользователей в облаке для Wallenc неприемлемо. Альтернатива *федеративное обучение* только на агрегированных градиентах по opt-in, без централизованного хранения файлов.
Общая схема on-device inference приведена на рис. @fig-36: метаданные и признаки не покидают устройство в открытом виде; обучение на расшифрованном содержимом всех пользователей в облаке для Wallenc неприемлемо. Альтернатива *федеративное обучение* только на агрегированных градиентах по opt-in, без централизованного хранения файлов.
#pz-fig("fig_36_ml_on_device.png", [Гипотетический контур on-device ML], "fig-36")
== Этика, ограничения и вывод по ВПК-2
Любой модуль ИИ должен быть отключаемым (opt-in), объяснять пользователю основание рекомендации (например, «похожее имя файла») и не подменять явное действие при удалении или синхронизации. Конфликт с E2E возникает, если отправлять ciphertext или ключи на внешний API inference в проекте такой путь не рассматривается.
Любой модуль ИИ должен быть отключаемым (opt-in), объяснять пользователю основание рекомендации (например, «похожее имя файла») и не подменять явное действие при удалении или синхронизации. Конфликт с E2E возникает, если отправлять ciphertext или ключи на внешний API inference в проекте такой путь не рассматривается.
*Вывод.* Компетенция «решать прикладные задачи анализа данных и принятия решений с использованием искусственного интеллекта» в пояснительной записке раскрыта анализом применимости ML к Wallenc: сформулированы задачи, источники данных, типы моделей, метрики обучения и границы развёртывания on-device. Внедрение в текущий MVP не выполнялось осознанно в соответствии с целями ВКР по защищённому хранению без доверенного backend.
*Вывод.* Компетенция «решать прикладные задачи анализа данных и принятия решений с использованием искусственного интеллекта» в пояснительной записке раскрыта анализом применимости ML к Wallenc: сформулированы задачи, источники данных, типы моделей, метрики обучения и границы развёртывания on-device. Внедрение в текущий MVP не выполнялось осознанно в соответствии с целями ВКР по защищённому хранению без доверенного backend.

View File

@@ -1,10 +1,12 @@
// Таблицы как в «Пример работы с Typst.typ» и gost: figure + table + table.header.
// Таблицы как в «Пример работы с Typst.typ» и gost: figure + table + table.header.
// ГОСТ 7.32 / чек-лист: в тексте и подписях среднее тире «–» (U+2013), не длинное «—».
#let pz-figure-caption-separator = [#sym.space.nobreak#sym.dash.en#sym.space.nobreak]
// Разрыв длинных таблиц и подпись сверху задаёт шаблон modern-g7-32 (style.typ).
#show table: set text(hyphenate: true, lang: "ru")
#show table.cell: set block(inset: (x: 5pt, y: 3pt))
// Таблица внутри figure по умолчанию один неразрывный block (обрезка длинных реестров).
// Таблица внутри figure по умолчанию один неразрывный block (обрезка длинных реестров).
#show figure.where(kind: table): set block(breakable: true)
#let pz-appendix-title(body) = heading(level: 1)[#body]
@@ -31,7 +33,7 @@
#let pz-thesis-topic-en = "Mobile application for secure storage of user data"
#let pz-thesis-subject = "мобильном приложении для защищённого хранения пользовательских данных"
// Бланк аннотации: 2×2 без рамок; левый столбец отступ, текст во 2-м (2 ячейки).
// Бланк аннотации: 2×2 без рамок; левый столбец отступ, текст во 2-м (2 ячейки).
#let pz-dept-mop = "кафедра МОП ЭВМ"
#let pz-dept-sait = "кафедра системного анализа и телекоммуникаций"
@@ -73,7 +75,7 @@
#linebreak()
#institute,
#linebreak()
#faculty, #department #year#sym.space.nobreak г.,
#faculty, #department #year#sym.space.nobreak г.,
#linebreak()
#pages#sym.space.nobreak с.
],
@@ -106,7 +108,7 @@
#linebreak()
#institute,
#linebreak()
#faculty, #department #year,
#faculty, #department #year,
#linebreak()
#pages#sym.space.nobreak p.
],
@@ -121,7 +123,7 @@
1
}
// Строка из ..range().map(i => ([a], [b])) в body.pos() один аргумент-массив.
// Строка из ..range().map(i => ([a], [b])) в body.pos() один аргумент-массив.
#let pz-flatten-cells(cells) = cells.fold((), (acc, cell) => {
if type(cell) == array { acc + cell } else { acc + (cell,) }
})

View File

@@ -8,6 +8,6 @@
*Перспективы развития*: расширение фонового расписания sync; поддержка дополнительных провайдеров; опциональные on-device модели по сценариям гл. 6; расширение автоматизированных UI-тестов.
Программная документация приведена в приложении Б; иллюстрации интерфейса в приложении В.
Программная документация приведена в приложении Б; иллюстрации интерфейса в приложении В.
По тестированию подтверждено: 68 модульных unit-тестов, инструментальные тесты Compose и Room, ручной протокол из двенадцати сценариев (гл. 5).

View File

@@ -4,22 +4,22 @@
*Актуальность* темы обусловлена распространением мобильных приложений для хранения файлов и секретов, а также ограниченностью готовых решений: многие продукты привязаны к собственному backend, закрытой экосистеме или узкой предметной области @google-secure-folder @bitwarden-help @cryptomator-docs.
*Цель работы* повысить конфиденциальность пользовательских данных при работе с недоверенными хранилищами за счёт разработки мобильного приложения для защищённого хранения пользовательских данных (Wallenc) без собственного сервера приложения, с иерархией vault storage файлы и клиентским шифрованием.
*Цель работы* повысить конфиденциальность пользовательских данных при работе с недоверенными хранилищами за счёт разработки мобильного приложения для защищённого хранения пользовательских данных (Wallenc) без собственного сервера приложения, с иерархией vault storage файлы и клиентским шифрованием.
Для достижения цели были поставлены следующие *задачи*:
+ выполнить анализ предметной области и сравнительный обзор аналогов, сформировать требования к программному продукту;
+ спроектировать архитектуру системы, модель данных и пользовательские сценарии;
+ реализовать программные модули приложения Wallenc на платформе Android (Kotlin);
+ провести тестирование программного обеспечения и оформить программную документацию.
- выполнить анализ предметной области и сравнительный обзор аналогов, сформировать требования к программному продукту;
- спроектировать архитектуру системы, модель данных и пользовательские сценарии;
- реализовать программные модули приложения Wallenc на платформе Android (Kotlin);
- провести тестирование программного обеспечения и оформить программную документацию.
*Объект исследования* методы и средства клиентской защиты данных в мобильных приложениях. *Предмет исследования* проектные и программные решения приложения Wallenc.
*Объект исследования* методы и средства клиентской защиты данных в мобильных приложениях. *Предмет исследования* проектные и программные решения приложения Wallenc.
*Методы исследования*: анализ нормативной и технической документации, сравнительный анализ программных аналогов, объектно-ориентированное проектирование (UML, BPMN, DFD), прототипирование пользовательского интерфейса, программная реализация и тестирование @gost7322017 @kotlin-docs.
*Практическая база.* Работа выполнена в рамках производственной практики в ООО НМФ «Нейротех» (09.02.202606.05.2026) по направлению 09.03.04 «Программная инженерия» на кафедре математического обеспечения и применения ЭВМ (МОП ЭВМ). Научный руководитель Беликов А. Н. (кафедра системного анализа и телекоммуникаций, САИТ); руководитель от организации Алексеев Д. М.
*Практическая база.* Работа выполнена в рамках производственной практики в ООО НМФ «Нейротех» (09.02.202606.05.2026) по направлению 09.03.04 «Программная инженерия» на кафедре математического обеспечения и применения ЭВМ (МОП ЭВМ). Научный руководитель Беликов А. Н. (кафедра системного анализа и телекоммуникаций, САИТ); руководитель от организации Алексеев Д. М.
*Научная новизна* заключается в сочетании единого `VaultsManager`, модели vault/storage, клиентского шифрования и адаптерного доступа к провайдерам без собственного сервера приложения, с проектным контуром синхронизации зашифрованных данных без передачи ключей провайдеру.
*Практическая значимость* использование результатов при дальнейшей разработке продукта и в учебных проектах по мобильной разработке и информационной безопасности.
*Практическая значимость* использование результатов при дальнейшей разработке продукта и в учебных проектах по мобильной разработке и информационной безопасности.
Пояснительная записка состоит из введения, шести глав, заключения, списка использованных источников и трёх приложений (листинги исходного кода, программная документация, скриншоты интерфейса). В главе 1 обоснована актуальность и приведено сравнение аналогов; глава 2 описывает архитектуру и модель Room; глава 3 UX и пользовательские сценарии; глава 4 реализацию по модулям, алгоритм синхронизации журналов и применение ИИ-ассистента при разработке; глава 5 тестирование; глава 6 вариативную профессиональную компетенцию по анализу данных и решениям с использованием ИИ.
Пояснительная записка состоит из введения, шести глав, заключения, списка использованных источников и трёх приложений (листинги исходного кода, программная документация, скриншоты интерфейса). В главе 1 обоснована актуальность и приведено сравнение аналогов; глава 2 описывает архитектуру и модель Room; глава 3 UX и пользовательские сценарии; глава 4 реализацию по модулям, алгоритм синхронизации журналов и применение ИИ-ассистента при разработке; глава 5 тестирование; глава 6 вариативную профессиональную компетенцию по анализу данных и решениям с использованием ИИ.

View File

@@ -1,7 +1,7 @@
#import "common.typ": pz-sig-line, pz-thesis-topic
// Строка ТЗ с линией на всю ширину (как бланк ВКР).
// Не входит в Пояснительная_записка_ПытковРЕ.typ черновик: front-matter-export/tz-vkr-assignment.typ
// Не входит в Пояснительная_записка_ПытковРЕ.typ черновик: front-matter-export/tz-vkr-assignment.typ
#let tz-row(body) = {
v(0.35em)
table(
@@ -52,7 +52,7 @@
#tz-row[2.2 ОС разработки: GNU/Linux / Windows 11.]
#tz-row[2.3 Язык программирования: Kotlin; целевая платформа Android (API 26+).]
#tz-row[2.3 Язык программирования: Kotlin; целевая платформа Android (API 26+).]
#tz-row[2.4 Стек и инструменты: Jetpack Compose, Kotlin Coroutines/Flow, Hilt, Room, JUnit, PlantUML.]
@@ -76,53 +76,53 @@
#tz-row[3.9 Список использованных источников]
#tz-row[3.10 Приложение А листинги исходного кода проекта Wallenc]
#tz-row[3.10 Приложение А листинги исходного кода проекта Wallenc]
#tz-row[3.11 Приложение Б программная документация (ТЗ, руководство пользователя)]
#tz-row[3.11 Приложение Б программная документация (ТЗ, руководство пользователя)]
#tz-row[3.12 Приложение В скриншоты пользовательского интерфейса]
#tz-row[3.12 Приложение В скриншоты пользовательского интерфейса]
#tz-row[4. Перечень графического материала:]
#tz-row-split(
[4.1 Цель, задачи и актуальность работы],
[ 1 пл.],
[ 1 пл.],
)
#tz-row-split(
[4.2 Контекстная диаграмма и анализ предметной области],
[ 1 пл.],
[ 1 пл.],
)
#tz-row-split(
[4.3 Обзор аналогов],
[ 1 пл.],
[ 1 пл.],
)
#tz-row-split(
[4.4 Требования к системе (функциональные и нефункциональные)],
[ 1 пл.],
[ 1 пл.],
)
#tz-row-split(
[4.5 Архитектура системы (BPMN, DFD, прецеденты, Clean Architecture, развёртывание, Room)],
[ 6 пл.],
[ 6 пл.],
)
#tz-row-split(
[4.6 Алгоритмы и диаграммы реализации (OAuth, шифрование, синхронизация журналов, зависимости модулей)],
[ 4 пл.],
[ 4 пл.],
)
#tz-row-split(
[4.7 Пользовательские сценарии и экраны UI],
[ 12 пл.],
[ 12 пл.],
)
#tz-row-split(
[4.8 Тестирование ПО],
[ 6 пл.],
[ 6 пл.],
)
#tz-row-split(
[4.9 Апробация результатов (производственная практика)],
[ 1 пл.],
[ 1 пл.],
)
#tz-row-split(
[4.10 Выводы по работе],
[ 1 пл.],
[ 1 пл.],
)
#tz-row[

View File

@@ -13,7 +13,7 @@
=== Назначение и цели создания системы
*Назначение* системы Wallenc предоставление пользователю мобильного клиента для работы с иерархией *VaultsManager → vault → storage → файлы*: один `LocalVault` на устройстве, удалённые vault по OAuth; внутри vault пользователь создаёт и управляет *storage*; шифрование (`StorageEncryptionInfo`, `Encryptor`) применяется к storage, а не к vault.
*Назначение* системы Wallenc предоставление пользователю мобильного клиента для работы с иерархией *VaultsManager → vault → storage → файлы*: один `LocalVault` на устройстве, удалённые vault по OAuth; внутри vault пользователь создаёт и управляет *storage*; шифрование (`StorageEncryptionInfo`, `Encryptor`) применяется к storage, а не к vault.
*Цели создания*: обеспечить единую модель vault и storage; минимизировать доверие к провайдеру; поддержать расширение списка провайдеров через адаптеры; сохранить служебные метаданные в локальной БД без хранения пользовательского контента в открытом виде.
@@ -25,7 +25,7 @@
[Свод функциональных требований],
2,
table.header([Код], [Требование]),
[ФР-1], [Создание, просмотр, переименование и удаление storage в локальном vault (LocalVault один на устройстве)],
[ФР-1], [Создание, просмотр, переименование и удаление storage в локальном vault (LocalVault один на устройстве)],
[ФР-2], [Включение шифрования storage, проверка ключа, открытие и закрытие зашифрованного представления],
[ФР-3], [Просмотр и операции с файлами внутри storage; текстовые секреты и 2FA],
[ФР-4], [OAuth-авторизация (Яндекс), регистрация удалённых vault и листинг их storage],
@@ -59,7 +59,7 @@
=== Требования к программно-аппаратной платформе
Минимальная платформа Android с поддержкой Jetpack Compose; для OAuth и удалённых операций требуется сетевое подключение. Объём оперативной памяти должен быть достаточен для фоновых задач шифрования и Room.
Минимальная платформа Android с поддержкой Jetpack Compose; для OAuth и удалённых операций требуется сетевое подключение. Объём оперативной памяти должен быть достаточен для фоновых задач шифрования и Room.
== Сравнение аналогов (обоснование требований)
@@ -67,7 +67,7 @@
[Сравнительная оценка аналогов],
6,
table.header([Критерий], [Secure Folder], [Proton], [Bitwarden], [Cryptomator], [Wallenc]),
[Собственный backend приложения], [], [+], [+/], [], [],
[Собственный backend приложения], [], [+], [+/], [], [],
[E2E / клиентское шифрование], [+/], [+], [+], [+], [+],
[Файловый vault], [+], [+], [], [+], [+],
[OAuth внешнего провайдера], [], [+/], [+/], [+/], [+],

View File

@@ -2,14 +2,14 @@
=== Цели и задачи испытаний
Основная цель подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI. Были поставлены следующие задачи:
Основная цель подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI. Были поставлены следующие задачи:
+ проверить `Encryptor` и проверку ключа для строк, байтов и потоков;
+ убедиться в корректном маппинге исключений в коды ошибок;
+ протестировать движок синхронизации (`StorageSyncEngine`, журнал, блокировки);
+ проверить оркестратор фоновых задач;
+ выполнить smoke-тесты навигации, deep link и 2FA/TOTP;
+ зафиксировать результаты ручных сценариев vault, OAuth и экрана задач.
- проверить `Encryptor` и проверку ключа для строк, байтов и потоков;
- убедиться в корректном маппинге исключений в коды ошибок;
- протестировать движок синхронизации (`StorageSyncEngine`, журнал, блокировки);
- проверить оркестратор фоновых задач;
- выполнить smoke-тесты навигации, deep link и 2FA/TOTP;
- зафиксировать результаты ручных сценариев vault, OAuth и экрана задач.
=== Объект и уровни тестирования

View File

@@ -1,10 +1,10 @@
== Автоматизированное тестирование (код модулей)
В проекте реализовано 68 автоматических unit-тестов в пяти модулях (`:domain` 12, `:domain-vault` 10, `:usecases` 25, `:ui` 15, `:task-runtime` 6). Тесты выполняются на JVM при сборке (`./gradlew test`). Инструментальные тесты размещены в `src/androidTest` соответствующих модулей.
В проекте реализовано 68 автоматических unit-тестов в пяти модулях (`:domain` 12, `:domain-vault` 10, `:usecases` 25, `:ui` 15, `:task-runtime` 6). Тесты выполняются на JVM при сборке (`./gradlew test`). Инструментальные тесты размещены в `src/androidTest` соответствующих модулей.
=== Листинги исходного кода автотестов
Ниже листинги файлов `src/test` и `src/androidTest` (при сборке PDF читаются из дерева проекта Wallenc, по тому же принципу, что приложение А пояснительной записки).
Ниже листинги файлов `src/test` и `src/androidTest` (при сборке PDF читаются из дерева проекта Wallenc, по тому же принципу, что приложение А пояснительной записки).
#include "../../listings/generated-tests/appendix-tests.typ"
@@ -38,6 +38,6 @@
== Инструментальные тесты (androidTest)
Запуск: `./gradlew connectedDebugAndroidTest`. Результат на рис. @fig-31-export.
Запуск: `./gradlew connectedDebugAndroidTest`. Результат на рис. @fig-31-export.
#pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest], "fig-31-export")

View File

@@ -1,6 +1,6 @@
== Отчёт о проведении тестирования
По итогам `./gradlew test` все 68 unit-тестов завершились со статусом PASSED. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room персистентность учётной записи Яндекс.
По итогам `./gradlew test` все 68 unit-тестов завершились со статусом PASSED. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room персистентность учётной записи Яндекс.
#import "../common.typ": pz-fig, pz-table

View File

@@ -4,7 +4,7 @@
[Свод функциональных требований (фрагмент ПЗ, гл. 1)],
2,
table.header([Код], [Требование]),
[ФР-1], [Создание, просмотр, переименование и удаление storage в локальном vault (LocalVault один на устройстве)],
[ФР-1], [Создание, просмотр, переименование и удаление storage в локальном vault (LocalVault один на устройстве)],
[ФР-2], [Включение шифрования storage, проверка ключа, открытие и закрытие зашифрованного представления],
[ФР-3], [Просмотр и операции с файлами внутри storage; текстовые секреты и 2FA],
[ФР-4], [OAuth-авторизация (Яндекс), регистрация удалённых vault и листинг их storage],

View File

@@ -18,6 +18,9 @@ echo "== render_puml =="
echo "== check_images =="
python3 "$SCRIPT_DIR/check_images.py"
echo "== check_gost_dashes =="
python3 "$SCRIPT_DIR/check_gost_dashes.py"
echo "== typst compile (тело ПЗ) =="
BODY_PDF="$REPORT_DIR/.Пояснительная_записка_ПытковРЕ.body.pdf"
cd "$REPORT_DIR"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Проверка: в .typ ПЗ нет длинного тире «—» (по чек-листу п. 29 — среднее «–»)."""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DIRS = [ROOT / "includes", ROOT / "appendices"]
SKIP = {"ch05-tests-generated.typ"}
EM = "\u2014"
def main() -> int:
bad: list[str] = []
for base in DIRS:
if not base.is_dir():
continue
for path in sorted(base.rglob("*.typ")):
if path.name in SKIP:
continue
text = path.read_text(encoding="utf-8")
for i, line in enumerate(text.splitlines(), 1):
if EM in line and "не длинное" not in line:
bad.append(f"{path.relative_to(ROOT)}:{i}: {line.strip()[:100]}")
if bad:
print("error: найдено длинное тире «—» (нужно среднее «–»):", file=sys.stderr)
for line in bad[:40]:
print(f" {line}", file=sys.stderr)
if len(bad) > 40:
print(f" ... и ещё {len(bad) - 40}", file=sys.stderr)
return 1
print("OK: длинное тире в .typ не найдено")
return 0
if __name__ == "__main__":
raise SystemExit(main())

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
#import "@preview/modern-g7-32:0.2.0": appendixes, enum-numbering, gost
#import "includes/common.typ": pz-appendix-title, pz-fig, pz-thesis-subject
#import "includes/common.typ": pz-appendix-title, pz-fig, pz-figure-caption-separator, pz-thesis-subject
#set enum(numbering: enum-numbering)
#set heading(numbering: "1.1.1.1")
@@ -29,6 +29,7 @@
)
#set text(font: "Times New Roman")
#set figure.caption(separator: pz-figure-caption-separator)
// Первые 3 страницы — front-matter-export/ВКР_первы_3_страницы_Пытков.pdf (qpdf в scripts/build*.sh)
#include "includes/pz-page-offset.typ"