Перейти к основному содержимому

Сборка ресурсов веб-интерфейса

Статус: принято 28 июля 2026 года.

Реализация: сборщик ресурсов добавлен 28 июля 2026 года. Скомпилированные пакеты не хранятся в Git. Исходный код и тесты определяют воспроизводимую сборку, проверку ресурсов со стороны Python, передачу между заданиями CI, содержимое sdist, отчёты SBOM и лицензий, а также порядок отката.

Контекст

Cockpit написан на React и TypeScript в web. Закреплённый npm-граф создаёт content-addressed и integrity-checked дерево assets внутри Python package. Установленный Harness wheel обслуживает его без Node.js и network access.

Сейчас compiled tree хранится в Git. Harness sdist намеренно не содержит frontend source и npm toolchain, но сохраняет compiled assets. Поэтому из него можно offline собрать wheel без Node.js, однако нельзя заново получить assets из authored frontend или доказать соответствие его revision.

Frontend asset producer должен удалить compiled JS, CSS, source maps и generated declarations из Git без потери следующих свойств:

  • clean source создаёт wheel и sdist;
  • wheel из sdist остаётся Node-free и обслуживает Cockpit offline;
  • Gateway и Harness можно собирать независимо;
  • отсутствующие, устаревшие или подменённые assets останавливают build, а не создают wheel со сломанным UI;
  • producer toolchain, licenses, SBOM, output hashes и release provenance проверяемы и воспроизводимы.

Offline build здесь означает, что все locked Python/npm inputs либо уже созданный verified asset artifact доступны локально. Это не обещание восстановить npm dependencies на пустой машине без cache или переданных artifacts.

Сравнение вариантов

КритерийPEP 517 Node build hookОтдельный versioned asset packageCI-injected verified assets
Clean-clone buildНеявно запускает npm внутри Python buildТребует два release artifactsЯвный producer step, затем обычный Python build
Offline Python buildТребует Node и npm cacheТребует локальный asset packageТребует только verified asset tree
Node availabilityНужен producer каждого wheel и sdistНужен только producer asset packageНужен только frontend producer job
Editable installСкрытые Node side effects при installVersion skew Python и assetsЯвная local frontend build в ignored staging tree
Wheel из sdistТребует frontend source, Node и npm inputsТребует ещё один точный packageИспользует sealed verified tree внутри sdist
Cache и reproducibilityPEP 517 cache скрывает второй package-manager graphХороши внутри artifact, но нужно синхронизировать две версииAsset digest служит cache key и Python input
Platform supportКаждый Python build platform требует Node toolchainRuntime portable, release ordering сложнееProducer работает на pinned platform, wheel остаётся pure Python
SBOM и licensesPython/npm evidence смешаны в opaque buildEvidence привязана к asset packageProducer публикует отдельную npm SBOM/license evidence
Release recoveryПовторить смешанный Python/Node buildСначала восстановить и выпустить точную asset versionПересобрать или восстановить commit-bound artifact, затем Python
Контракт репозиторияДва package, но PEP 517 запускает NodeТретья независимо versioned distributionДва workspace member и один Harness runtime artifact

PEP 517 Node build hook

Hatch build hooks умеют добавлять ignored generated artifacts и force-include файлы в build target. Они подходят для validation и inclusion, но запуск npm ci и Vite внутри hook сделает Node и npm-граф неявными требованиями каждого isolated Python source build. PEP 517 build isolation не описывает и не устанавливает такой non-Python graph. Wheel из sdist также потребует Node и frontend source, а editable install получит скрытые side effects.

Вариант отклонён. Frontend asset producer может добавить Python-only Hatch hook для проверки и включения уже созданного дерева; это consumer guard, а не Node build hook.

Отдельный versioned asset package

Выделенный asset wheel или archive даёт frontend собственную release identity, но GigaLoom не требует независимого runtime rollout browser shell. Появятся третья distribution, exact-version coupling, release ordering, дополнительный offline-install input и новый recovery path для asset/Python skew. Текущий two-member workspace и один Harness artifact проще и уже дают нужную runtime границу.

Вариант отклонён. К нему можно вернуться только при самостоятельном release cadence Cockpit или нескольких Python consumers.

CI-injected verified assets

Frontend producer и Python consumer остаются разными build stages. Producer использует pinned Node/npm graph и создаёт одно полное дерево Cockpit. CI передаёт его как commit-bound, content-addressed build artifact в Python build, который проверяет и включает дерево в Harness wheel и sdist. Asset artifact — build input, а не отдельная опубликованная runtime dependency.

Этот вариант выбран.

Решение

Frontend asset producer реализует следующий контракт.

  1. Authored TypeScript, configuration, npm lockfile, canonical brand source и deterministic producer scripts остаются tracked. Compiled output создаётся в ignored staging tree по существующему Python resource path, чтобы installed и editable loaders использовали один путь.
  2. Одна документированная producer command начинает с чистого staging tree, выполняет pinned frontend build и создаёт существующий per-file integrity manifest плюс content-free provenance. Provenance связывает Git revision, frontend input digest, lockfile digest, canonical brand digest, версии Node/npm, output-tree digest и SBOM/license evidence.
  3. CI и release workflow запускают producer в отдельном Node job и передают точное дерево и provenance в Python artifact job. Передача проверяется по hash и привязана к той же source revision; mutable “latest” assets не загружаются.
  4. Python-only Hatch consumer проверяет manifest, размеры и hashes всех файлов, полный allowlisted tree, provenance, source revision при наличии authored source, а также отсутствие symlinks, path escapes и неожиданных файлов. Missing или stale input завершается ошибкой с точной local recovery command.
  5. Verified tree включается в direct wheel и sdist. Sdist остаётся sealed Python source artifact: содержит verified Cockpit tree и consumer metadata, но не frontend toolchain. Wheel из sdist не требует Node или network.
  6. Editable development использует тот же ignored staging path. Явная frontend producer command атомарно обновляет его; verifier отвергает source/provenance mismatch, поэтому старый local output не может затенить новый authored source.
  7. Gateway build остаётся полностью независимым. Harness build имеет два явных режима: produce-and-package из source checkout либо consume ранее проверенного дерева из того же CI/release run.
  8. Release evidence совместно хранит asset digest, npm SBOM/license report, hashes Python wheel/sdist и provenance attestation. Rollback берёт source прошлого release и либо детерминированно пересобирает, либо восстанавливает его commit-bound verified asset artifact до Python build.

Consumer никогда не запускает npm, не обращается к registry, не принимает unsigned или mutable asset location и не создаёт wheel без Cockpit.

Evidence spike

Локальный архитектурный spike использовал Git archive принятого asset-contract revision и удалял compiled assets только внутри временной копии.

  • npm ci --offline --ignore-scripts восстановил 301 package из local cache, а production producer заново создал 53 packaged asset files.
  • Обычный frontend gate прошёл 37 test files и 137 tests. Две production build дали одинаковый asset-tree digest: 997003971b3db91353df7d11410f4250d468a596ee6ee1388ee342a3a5a6ec9b.
  • Direct injected Harness wheel и Node-free wheel из его sdist были побитово одинаковы: 4d6773a1edef8a65f4de02edb75d8fc522aed474f9213895340f64d1f96f1af0.
  • Sdist сохранил verified asset manifest и не включил frontend toolchain.
  • Build того же temporary source с пустым asset directory сейчас успешно создаёт неполный wheel. Это доказывает, что fail-closed Python consumer обязателен до удаления tracked output.

Локальный spike использовал Node.js 22.12, тогда как repository contract и CI требуют 22.13 или новее; npm сообщил о mismatch. Это окружение не принимается как release evidence. Asset producer должен выполнить reproducibility, platform, SBOM/license и release recovery gates на pinned toolchain.

Последствия

Python package build остаётся deterministic, offline-capable и Node-free при получении verified tree. Frontend production становится явным supply-chain stage с проверяемой evidence, а не implicit side effect или tracked source.

Цена решения — новый staging/verification contract и CI artifact handoff. Asset producer должен реализовать и проверить их до удаления любого tracked compiled file. До этого текущий bundle остаётся rollback и packaging source.

Ссылки