Releases and vendoring
Tagged releases build a development model and a runtime model after the CI gates pass, check_runtime.py proves the runtime model holds no development code, and vendor.py syncs a reviewed vendor copy without overwriting local edits.
A pushed v* tag produces two Roblox models on the GitHub release: the whole project, and a runtime-only framework model without specs, benches, TestKit or the Simulation showcase. The release job runs the full check first and publishes nothing when a gate fails. For games that keep their own copy of the framework source, tools/vendor.py updates that copy only where it still matches the last reviewed baseline, and stops before writing anything when it finds local edits. The long-form notes are docs/vendoring.md in the repository; the per-version changes are on Changelog.
The two models
Section titled “The two models”| Artifact | Built from | Contents |
|---|---|---|
ChloeKernel-<tag>.rbxm |
build.project.json |
A folder holding ReplicatedStorage (src/Shared), ServerScriptService (src/Server) and StarterPlayerScripts (src/Client): the framework, TestKit, every spec and bench, Simulation, the Main boot scripts, the Bootstraps and the Game layer. |
ChloeKernelRuntime-<tag>.rbxm |
runtime.project.json |
A folder holding ReplicatedStorage.ChloeKernel and ServerScriptService.ChloeKernelServer only. Tests, Benches, TestKit and Simulation are excluded by glob. The disabled ActorPool worker template stays. No boot scripts. |
Insert either model into a place to pin an exact framework version. With the runtime model, move each folder’s children into the matching service and supply your own boot script: it boots nothing by itself. The runtime model keeps the Debug modules, so the debug panels and their access gate ship with it.
To build locally:
rojo build build.project.json -o ChloeKernel.rbxmrojo build runtime.project.json -o ChloeKernelRuntime.rbxmThe release job
Section titled “The release job”.github/workflows/release.yml runs on any pushed tag matching v*, with permission to write releases:
- The
checksjob calls.github/workflows/ci.ymlas a reusable workflow, which runspython tools/check.pywith the pinned toolchain and the verified Luau runtime. - The
releasejob needschecksto pass. It installs theaftman.tomlpins and buildsChloeKernel-${GITHUB_REF_NAME}.rbxmfrombuild.project.jsonandChloeKernelRuntime-${GITHUB_REF_NAME}.rbxmfromruntime.project.json. - It extracts the
CHANGELOG.mdsection whose heading starts with## <version>(the tag without itsv) intoRELEASE_NOTES.md, or writesSee CHANGELOG.md.when there is none. - It runs
gh release create <tag>with both models, the titleChloeKernel <tag>and those notes.
Before 0.8.0 a tagged release built its single model without running the gates; 0.8.0 made the release depend on them and added the runtime model.
Verifying the runtime model
Section titled “Verifying the runtime model”A successful Rojo build does not prove the ignore globs removed anything. The check’s runtime step builds runtime.project.json to a temporary .rbxmx and runs tools/check_runtime.py on the XML, which asserts:
- no script source sits under a path containing
Tests,Benches,TestKitorSimulation, - every source that is not a ModuleScript is the
ActorPool/WorkerScript, and itsDisabledproperty istrue, - the model contains the modules a runtime needs:
ChloeKernel,ChloeKernel/Scope,ChloeKernel/Scheduler,ChloeKernel/Net/Packet,ChloeKernelServer,ChloeKernelServer/DataandChloeKernelServer/Commerce/Receipts.
On success it prints Runtime model verified: <n> sources; no tests, benches, TestKit, Simulation or active bootstraps. Any failed assertion fails the check, and with it the release.
Debug access in a runtime copy
Section titled “Debug access in a runtime copy”NetTap.OwnerUserIds lists operators who are always allowed the debug surface (every player’s decoded NET traffic, the server log tail and the SIM CONTROL load controls) when ChloeKernelDebug is true, whatever ChloeKernelDebugUserIds says. It always includes CryptedChloes (7241279999), the framework’s only developer: debugAllowed checks that id itself, so no edit to the table can drop it, and directive CK017 rejects code in any file that removes or replaces the entry. A game adds further operators from shared bootstrap code, so the server and clients agree: NetTap.OwnerUserIds[123456789] = true. See Production access control.
Vendoring with vendor.py
Section titled “Vendoring with vendor.py”tools/vendor.py keeps a game’s vendored copy of the framework in step with this repository without overwriting edits made in the game. It maps two source folders onto the vendor layout of the game it was written for:
| Framework source | Vendor path in the game |
|---|---|
src/Shared/ChloeKernel |
Src/ReplicatedStorage/ChloeKernel |
src/Server/ChloeKernelServer |
Src/ServerScriptService/ChloeKernelServer |
Simulation/init.luau maps to a flat Simulation.luau, which keeps that game’s existing ModuleScript identity. The mapping is fixed in the script; a game with another layout needs its own mapping.
python tools/vendor.py check --game /path/to/game # the default actionpython tools/vendor.py record --game /path/to/gamepython tools/vendor.py sync --game /path/to/gamepython tools/vendor.py sync --game /path/to/game --source /path/to/other/framework/checkoutIt compares file hashes (SHA-256 after normalizing CRLF to LF) against chloekernel.vendor.json in the game, the manifest of the last reviewed baseline, and classifies every mapped file:
| State | Meaning |
|---|---|
same |
The vendor file equals the framework file. |
upstream changed |
The vendor file still matches the baseline and the framework moved on. Safe to sync. |
new upstream file |
The framework has a file the baseline never had and the game does not have. Safe to sync once a manifest exists. |
local changes |
The vendor file differs from the baseline: someone edited it in the game. |
unreviewed local file |
The vendor file exists but the baseline never recorded it. |
It also lists vendor modules the framework does not have and baseline files the framework removed, as requires manual review (not deleted). It never deletes a module.
checkprints every difference and<k>/<n> mapped files match (line endings ignored). It exits 1 on any difference, any unknown or removed module, or a missing manifest.recordwrites the manifest for a reviewed baseline. It refuses while any mapped file differs, so it records only copies that already agree.synccopies upstream changes. It refuses before writing anything when it sees local changes, unreviewed local files, unknown vendor modules or removed upstream modules, and a first sync needs a recorded manifest.
The first reconciliation is manual: run check, merge the reusable fixes from the game’s copy into the framework, never copy an old fork over newer security or lifecycle fixes, and record once every file agrees. Commit chloekernel.vendor.json with the game. It holds each file’s upstream path and hash and no machine paths. After that, a framework update is check, sync, the game’s own checks, and a commit.
How sync writes
Section titled “How sync writes”- Before touching anything, it re-reads every destination and the manifest and aborts if any changed since it inspected them.
- It copies the original bytes of every file it will replace, and the old manifest, into
.chloekernel-vendor-backups/<UTC time>-<12 hex digits>/in the game, with abackup-index.jsonlisting their hashes and the new files it will add. There is no automatic rollback. - It writes each file to a temporary file in the destination folder, flushes it to disk, checks once more that the destination still holds the bytes it read, and moves the temporary file over it.
- It writes the new manifest last.
A conflict in the middle of a sync can leave earlier files updated, with their originals in the backup folder and the old manifest still in place. Review and reconcile that partial state before recording a new baseline. The tool makes each file write atomic, not the update as a whole; keep a clean commit in the game before you sync.
tools/test_vendor.py exercises conflict refusal, concurrent edits, path confinement, backups, line endings, metadata and the Simulation mapping without touching a real project. It runs in the vendor step of the check. To run the core regression runner against a vendored copy, pass --root /path/to/game to tools/regressions/core.py (see Regression runners).