# Build contract — Phase 2 Из корня проекта, с Node.js 22+ и npm: ```bash npm ci --ignore-scripts scripts/spike-tools.sh ./mcpack build ``` Дополнительные параметры: ```bash ./mcpack build --config platform/config.yaml ./mcpack build --release ``` `--config` выбирает YAML manifest; относительные пути pack/output внутри него считаются от корня репозитория. По умолчанию build разрешает dirty worktree и записывает это в manifest. `--release` требует чистого worktree. Команды не делают commit/tag/deploy и не запускают Minecraft. Имя, версия, Minecraft и loader читаются из TOML packwiz. Полный релиз создаётся в `/-/` и содержит: ```text -.mrpack server/compose.yaml server/.env.example server/config/ server/README.md manifest.json SHA256SUMS ``` Релиз той же версии не перезаписывается. Для изменённого source нужно выбрать новую версию pack, явно выполнить packwiz refresh, затем build. Для локального эксперимента с прежней версией можно выбрать другой output directory через отдельный config. Не изменять опубликованный release directory вручную. ## Pipeline 1. Parse/validate YAML и TOML: обязательные поля, индексы/hash, explicit side, дубликаты mod targets/project IDs, пути, подтверждённый loader/image. 2. Проверить SHA256 packwiz/Compose по lock и Git state/известные secret paths. 3. Получить lock конкретной версии и временный каталог в output directory. 4. Скопировать pack; выполнить packwiz refresh/export. Любое изменение metadata после refresh останавливает build, исходники не исправляются. 5. Проверить экспорт, сформировать серверные файлы и выполнить Compose config. 6. Записать manifest, покрыть SHA256SUMS все release files кроме SHA256SUMS; проверить checksums и неизменность build inputs. 7. Атомарно переименовать готовый временный релиз; убрать временные файлы/lock. При обычной ошибке partial release не остаётся. После принудительного убийства процесса может остаться `.build-*.lock`/временный каталог: удалять их только убедившись, что соответствующий build уже не работает. Детерминированными являются имя и размещение артефактов. Побайтовая воспроизводимость ZIP не обещается: packwiz может включать timestamps. ## Server payload Compose образ закреплён literal digest из toolchain lock. Memory/port defaults берутся из platform config; local env может переопределять их при запуске. Runtime и release подключаются через абсолютные `MC_ROOT` и `MC_RELEASE_DIR`. Release не зависит от checkout или его `.tools/`. Используется стабильное Compose project name `minecraft-lab-spike`, как в текущем локальном runtime. `server/config/` включается в релиз и монтируется как `/config`: его файлы имеют приоритет над соответствующими pack defaults. Локальный исходный Compose spike по-прежнему включает этот слой только явным override. Server properties генерируются environment, отдельного source-файла нет. `.env`, активный локальный compose override, SSH target и platform YAML не копируются в release. Публичный `.env.example` имеет EULA=FALSE. Проверка известных secret filenames не является универсальным secret scanner. ## Manifest v1 - `pack`: name/version, Minecraft, loader ID/version. - `git`: commit и dirty flag; generated output исключается из dirty check. - `tools`: packwiz commit/hash, Compose version/hash, Node version/npm lock hash. - `server`: image, подтверждённая Java major, defaults и project name. - `source_files`: относительные пути inputs и SHA256, без содержимого config. - `managed_files.client/server`: runtime paths, owner (`pack`/`server`), hashes и политика replace; server override помечает заменённого владельца. - `runtime_policy`: неизвестные файлы preserve, defaultconfigs seed-only. - `release_files`: hashes payload без manifest и SHA256SUMS, чтобы избежать циклического хеширования. SHA256SUMS дополнительно покрывает manifest. Ownership описывает контракт для будущего deploy; Phase 2 сама ничего не удаляет из runtime. Совместимость Java проверяется по записи Phase 0 в `platform/compatibility.json`, а не новым запуском Docker при каждом build. Новая комбинация требует отдельного spike и записи результата.