Воспроизводимость и происхождение
Kshana создан воспроизводимым: одни и те же входные данные дают одни и те же результаты, а артефакты сборки можно отследить до исходного кода, из которого они получены. В этом документе точно указано, что гарантируется, что нет и как гарантии обеспечиваются.
Гарантии детерминированности#
В движке нет обращений к настенным часам, зависимости от порядка выполнения потоков и неинициализированной случайности. Каждый стохастический процесс управляется потоком ChaCha8Rng, привязанным к seed сценария, и отсчёты берутся в фиксированном порядке. Следствия:
| Свойство | Гарантируется | Чем обеспечивается |
|---|---|---|
Тот же сценарий, та же машина → побайтово идентичный result.json |
Да | scripts/check-reproducible.sh (дважды запускает эталонный сценарий, сравнивает SHA-256 (SHA: Secure Hash Algorithm)) |
| Тот же сценарий → идентичные показатели качества поле за полем | Да, для каждой платформы | tests/golden.rs фиксирует каждый показатель качества (FoM) для четырёх эталонных сценариев |
Хеш входных данных сценария (scenario_hash) не зависит от платформы |
Да | SHA-256 канонического сценария, адресуемый по содержимому и зафиксированный в tests/golden.rs |
| Отпечаток входных данных и форма выходных данных идентичны на всех ОС (операционная система) | Да | tests/cross_platform_golden.rs фиксирует точный SHA-256 для каждого сценария (десять сценариев, по одному файлу .sha256 в tests/golden/), проверяется в матрице непрерывной интеграции (CI) для 3 ОС |
| Значения выходных данных совпадают на всех ОС (ubuntu/macOS/Windows) | Да, с точностью до 1e-6 | задание CI reproducibility-matrix запускает golden.rs (1e-6), sgp4_verification.rs (2e-5 км) и determinism.rs на всех трёх ОС |
| Кадр навигационного сообщения на низкой околоземной орбите (LEO) состоит из одних и тех же байтов на любой платформе и в любом профиле сборки, включая сборку WebAssembly (WASM) | Да | src/portable_math.rs (каждая трансцендентная функция и каждое нормальное отклонение вида leo-navmsg проходят через крейт libm на чистом Rust); leo_navmsg::tests::the_encoded_frame_is_the_same_bytes_on_every_platform фиксирует весь кадр без ограничения по платформе |
| Выборки индексов с начальным значением (бутстрэп-перевыборка, перемешивания) одинаковы на 32-разрядных и 64-разрядных целевых платформах | Да | portable_math::uniform_index всегда выбирает как u64; зафиксировано в portable_math::tests |
| Один и тот же набор инструментов везде | Да | rust-toolchain.toml закрепляет канал; scripts/check-toolchain.sh завершает сборку ошибкой при расхождении; CI и выпуск закрепляют ту же версию |
| Один и тот же набор зависимостей | Да | Cargo.lock зафиксирован в репозитории, а cargo metadata --locked используется для SBOM (перечень компонентов программного обеспечения) |
Каждый запуск из командной строки также записывает в свой отчёт запись о воспроизводимости (<scenario>.report.json и .report.html): версию движка, SHA-256 файла сценария и документа результата, seed, платформу и точную команду, которую tests/advanced_report_cli.rs повторно запускает до побайтово идентичного результата. Сам отчёт — чистая функция прогона: два запуска scenarios/clock-holdover.toml записывают один и тот же report.json байт в байт. См. REPORTS.md.
Оговорка о разных платформах (и как с ней справляются эталонные тесты)#
Численные результаты побитово идентичны на данной платформе, но могут отличаться в последних нескольких единицах последнего разряда (ULP) между платформами. Причина — математическая библиотека платформы: sqrt, ln, exp и подобные функции по IEEE-754 (IEEE: Institute of Electrical and Electronics Engineers) не обязаны округляться правильно, поэтому Linux (glibc, библиотека GNU C), macOS и Windows могут возвращать разный последний бит. На длинном прогоне эти различия порядка ~1e-16 накапливаются, возможно, до ~1e-12 в относительном выражении.
Два обстоятельства делают это различие в последнем разряде больше, чем оно было вначале, и оба измерены, а не предполагаются. Адаптивный интегратор выбирает следующий шаг по оценке ошибки через корень пятой степени, поэтому одна единица последнего разряда меняет последовательность шагов, и тогда две платформы согласуются лишь с точностью до допуска интегрирования. Итеративная оценка (подгонка методом наименьших квадратов, фильтр определения орбиты) может прийти к сходимости другим путём. Виды сценариев, построенные на том или другом, могут отличаться между платформами более чем на 1e-6 в отдельных полях; src/test_support.rs фиксирует тот же режим для модульных тестов.
Профиль сборки влияет так же, на одной машине. Оптимизированная сборка на macOS объединяет синус и косинус одного аргумента в один вызов комбинированной системной процедуры, синус которой отличается от отдельного sin в последнем бите для 379 из 200 000 аргументов; неоптимизированная сборка делает два вызова. По измерениям на aarch64 macOS на 138 файлах сценариев отладочная и релизная сборки одного исходного кода дают побайтово идентичные документы результата для 102 из них. Остальные 36 отличаются в последних разрядах: 28 — в пределах 1e-6, один (leo-ppp-convergence) — на 2.8e-6, а семь кампаний — только в контрольных суммах прогонов-участников; ни в метке, ни в счётчике, ни в вердикте различий нет. Поэтому гарантия побайтовой идентичности выше относится к одной сборке: к одному и тому же двоичному файлу, запущенному дважды.
Нормальные выборки с начальным значением — третий, гораздо более редкий источник: в редких ветвях генератор вызывает экспоненту и логарифм хоста, и примерно одна выборка из миллиона отличается в последнем бите между нативной сборкой и сборкой WASM.
Если выходные данные дискретны, различие в последнем разряде недопустимо вовсе, и вид сценария вычисляется с помощью src/portable_math.rs, а не библиотеки хоста. Первым так устроен вид leo-navmsg: его двоичный кадр — это передаваемые целые числа, и один и тот же сценарий однажды закодировался в три разных кадра: в релизной сборке, в отладочной сборке того же исходного кода и в браузере. Теперь во всех трёх случаях получаются одни и те же байты.
Поэтому эталонные тесты не фиксируют единый межплатформенный хеш выходных данных с плавающей запятой: это было бы хрупко и приводило бы к отказу вполне корректных сборок на другой ОС. Вместо этого:
tests/golden.rsфиксирует каждый показатель качества с относительным допуском1e-6— на четыре порядка жёстче любой реальной регрессии (которая сдвигает значение на целые проценты), но намного мягче колебаний математической библиотеки (libm) на разных платформах. Поля, ограниченные сеткой (секунды holdover), и поля с точным нулём фиксируются точно.scenario_hash— хеш по содержимому входных данных — не зависит от платформы и фиксируется точно.tests/cross_platform_golden.rsфиксирует точный SHA-256, хранящийся для каждого сценария вtests/golden/, для проекции, которая действительно идентична на всех платформах: отпечаток входных данных плюс форма выходных данных (имена полей, вложенность, типы листьев, длины массивов — определяемые детерминированной арифметикой сетки, но никогда не значения с плавающей запятой). Так структурные регрессии отлавливаются точно, а допуски выше отлавливают регрессии значений.scripts/check-reproducible.shобеспечивает побайтово идентичный вывод при двух запусках на одной машине (гарантия детерминированности).- Задание CI
reproducibility-matrixзапускает четыре перечисленных выше теста (cross_platform_golden,golden,determinism,sgp4_verification) на ubuntu-latest, macos-latest и windows-latest, поэтому межплатформенная воспроизводимость проверяется при каждой отправке: эталоны формы и входных данных — точно, численные значения — с точностью до 1e-6, а состояния SGP4 (Simplified General Perturbations 4) — до 2e-5 км, а не опирается на одну ОС.
Если нужно заново сформировать зафиксированные числа (например, после намеренного изменения модели), запустите каждый эталонный сценарий и скопируйте выведенные значения FoM в tests/golden.rs, а затем отметьте изменение в CHANGELOG.md.
Перечень компонентов программного обеспечения (SBOM)#
scripts/gen-sbom.sh формирует SBOM в формате CycloneDX 1,5 со списком каждого крейта, входящего в артефакт kshana, с точной версией, источником и лицензией. Это генератор без зависимостей, построенный на cargo metadata --locked (так что он работает с одним лишь набором инструментов), и он обходит граф разрешения зависимостей от пакета kshana только по обычным зависимостям и зависимостям сборки. Зависимости для разработки (крейты только для тестов, такие как sgp4) исключены, поскольку ни один артефакт их не содержит.
Граф — это объединение наборов функций, с которыми собираются поставляемые артефакты: сборка по умолчанию (библиотека и интерфейс командной строки (CLI)), --features python (пакет-колесо PyPI (Python Package Index), цепочка pyo3 которого образует границу внешних функций) и --features wasm (пакет npm, который несёт этот же SBOM внутри архива). Включены зависимости, зависящие от платформы, для каждой целевой платформы, поскольку колёса поставляются для нескольких операционных систем, а пакет npm нацелен на WebAssembly. Один документ охватывает все три артефакта, поэтому для любого из них он является надмножеством. Сейчас он перечисляет 107 компонентов; это число зафиксировано в tests/fixtures/reproducibility_software_assurance/. У отдельного сервера kshana-mcp собственный манифест, и этот SBOM его не описывает.
Вывод детерминирован: один и тот же набор зависимостей даёт побайтово идентичный документ, включая серийный номер, полученный из отсортированного списка пакетов, а не из отметки времени.
Рабочий процесс выпуска формирует SBOM (kshana-sbom.cdx.json) и прикрепляет его к каждому помеченному выпуску; рабочий процесс публикации также поставляет его рядом с колёсами PyPI и внутри пакета npm.
Происхождение сборки#
Рабочий процесс выпуска создаёт аттестацию происхождения сборки SLSA (Supply-chain Levels for Software Artifacts) (actions/attest-build-provenance), охватывающую каждый файл, который он прикрепляет к выпуску на GitHub: двоичные файлы командной строки для каждой платформы, двоичный файл сервера kshana-mcp, SBOM, сводку валидации и SHA256SUMS. Потребитель может с помощью gh attestation verify проверить, что артефакты собраны рабочим процессом выпуска этого репозитория из данного исходного кода, — это закрывает разрыв между «вот двоичный файл» и «вот двоичный файл, который я могу проследить до исходного кода». Например, для SBOM выпуска v0.28.0:
curl -sLO https://github.com/AshfordeOU/kshana/releases/download/v0.28.0/kshana-sbom.cdx.json
gh attestation verify kshana-sbom.cdx.json --repo AshfordeOU/kshana