kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

Releasing and updating kit

kit ships itself. A kit release is just a signed kit package: the same content-addressed .kpkg/.tar.gz machinery that packages user code (DISTRIBUTE.md) is dogfooded onto the toolchain, and a running kit verifies and installs a newer kit with kit update. There is no second mechanism and no new trusted-byte surface beyond the existing minisign/Ed25519 path. This document is the operational reference; the cross-cutting release punchlist lives in plan/RELEASE.md.

Versioning

The single source of truth is the committed top-level VERSION file holding a CalVer string YYYY.MINOR.PATCH (YYYY = release-series year, MINOR = release within the year, PATCH = fix release). Nothing derives the version from git tags; an annotated tag v<VERSION> is created at release time from VERSION, not the reverse.

kit --version prints the version plus provenance, and every tool answers it the same way (so toolchain probes like cc --version work):

$ kit --version
kit 2026.6.0 (a3f91c2, aarch64-macos)
$ cc --version
kit cc 2026.6.0 (a3f91c2, aarch64-macos)

Mechanism: mk/flags.mk reads VERSION + the git short hash once per make invocation and injects them as -D defines into the single TU driver/version.c (kit_version_string / kit_build_id / kit_host_triple), gated on a dedicated .version-config sentinel so a version or commit change recompiles only that one object. There is no __DATE__/__TIME__ anywhere, so a given tree always embeds the same bytes and the self-host byte-identity gate is preserved.

Building release artifacts

Both artifact paths require explicit release signing and update-channel trust configuration. Hermetic tests opt into the checked-in non-release key; no release target selects it implicitly.

make dist — native host

Builds RELEASE=1, stages a self-contained kit/ tree, and emits — for the native host triple — a signed fat .kpkg, a signed portable .tar.gz, and a detached .tar.gz.minisig:

build/dist/
  kit-2026.6.0-aarch64-macos.kpkg            native fat, signed
  kit-2026.6.0-aarch64-macos.tar.gz          portable, signed (manifest sig embedded)
  kit-2026.6.0-aarch64-macos.tar.gz.minisig  detached sig over the tarball (bootstrap)

The staging tree is bin/kit, lib/libkit.a, include/, support/rt/{include,lib}/, doc/, VERSION, and README/license. The documentation tree includes the maintained references plus the explicitly forward-looking plan/ and ideas/ indexes; the historical release audit is not shipped. The overview is README.md. The target requires KIT_SIGN_KEY=<path>, KIT_RELEASE_PUBKEYS=<path...>, and KIT_UPDATE_INDEX_URL=<stable-url>. It rejects the in-tree NON-RELEASE test key unless a hermetic harness explicitly sets KIT_RELEASE_ALLOW_TEST_KEY=1. For a local packaging smoke test, make dist-dev invokes make dist with the checked-in non-release keypair and an inert example.invalid update URL. The .kpkg is the canonical update unit; the .tar.gz carries its signed manifest internally, and the extra detached .minisig lets a user with only stock minisign verify before unpacking.

scripts/release.sh — the multi-target matrix

For each hosted (runnable) triple in scripts/hosted.sh's support set, release.sh cross-builds the kit binary + per-target rt archives (via kit_cross.sh and, for foreign libc, the sysroots of plan/SYSROOTS.md), stages and packages each exactly as make dist does, signs everything with the real release key (KIT_SIGN_KEY, held in CI/secret, never in-tree), and emits + signs the channel index. It asserts a clean git tree and refuses the in-tree test key. Freestanding targets have no host to run on and produce no artifact. Knobs: KIT_SIGN_KEY (required), KIT_RELEASE_URL_BASE (mirror base URLs), KIT_RELEASE_TARGETS, KIT_VM=0 (drop FreeBSD/Windows on a VM-less runner), KIT_CHANNEL, KIT_RELEASE_OUT_DIR (artifact output directory, default build/release-dist). Hermetic tests that intentionally sign with the in-tree non-release key must also set KIT_RELEASE_ALLOW_TEST_KEY=1.

Targeted validation for the runnable arm64 macOS artifact is make test-release-macos-aa64: it drives scripts/release.sh for macos-aa64 with the in-tree non-release key, installs the .kpkg under an isolated KIT_HOME, unpacks the .tar.gz under a separate isolated KIT_HOME, and runs both resulting kit binaries.

Detached signatures over arbitrary files (the .tar.gz.minisig and the index signature) are produced by kit pkg sign -s KEY [-o OUT] FILE, which writes a stock-minisign-compatible signature.

Channel index and hosting

Each channel (normally stable) is described by a small signed, byte-stable text file, kit-release 1, emitted/parsed by libkit (src/dist/release.c, kit_release_index_*):

kit-release 1
channel = stable
version = 2026.6.0
hash = blake2b-256

[host]
target = aarch64-macos
kpkg   = <64-hex package-id of the .kpkg>
targz  = <64-hex package-id of the .tar.gz>
size   = 9311461
url    = https://github.com/<org>/kit/releases/download/v2026.6.0/kit-2026.6.0-aarch64-macos.kpkg
url    = https://mirror.example/kit/2026.6.0/kit-2026.6.0-aarch64-macos.kpkg

Hosting is not a fixed location: each artifact is identified by its content id (package-id), and the repeated url lines are an ordered, untrusted mirror list. The fetcher tries them in order and accepts bytes only after they match the signed content id and the package signature, so any host — GitHub Releases, a CDN, an S3/R2 bucket, a private mirror — is interchangeable. The index may be served over plain HTTP.

Trust, keys, and rotation

Release artifacts are signed with a dedicated minisign (Ed25519) keypair whose secret is held in CI/secret and never enters the tree. The binary embeds an array of trusted release public keys (driver/release_key.c); a signature from any embedded key verifies an update, which gives key rotation for free: ship release N+1 signed by a key already embedded in N, and introduce the next key in a later overlap window. Verifying an update requires the signature to validate against an embedded (or --key-supplied) key and the fetched package-id to match the signed channel-index entry and the version to pass policy (below).

The published release public key + the detached .tar.gz.minisig give the bootstrap path when no kit exists yet:

$ minisign -Vm kit-2026.6.0-aarch64-macos.tar.gz -P <published kit release pubkey>
$ tar xzf kit-2026.6.0-aarch64-macos.tar.gz && kit/bin/kit --version

From then on the installed kit's embedded anchor verifies all future updates. (The in-tree default anchor is the NON-RELEASE test key; the real release pubkey replaces/augments it at release time.)

Installing and updating

kit update manages a single self-contained root, $KIT_HOME (default $XDG_DATA_HOME/kit~/.local/share/kit; %LOCALAPPDATA%\kit on Windows):

$KIT_HOME/
  versions/2026.6.0/{bin,lib,include,support,VERSION}
  current  -> versions/2026.6.0          atomic active-toolchain pointer
  bin/                                   PATH links -> ../current/bin/kit
  config/trusted_keys                    pkg trust anchors
  cache/downloads/                       fetched kpkgs / scratch

Put $KIT_HOME/bin on PATH once. An update verifies the package, unpacks it into a temp dir, renames it into versions/<new>, flips current atomically, and refreshes the bin/ links — the live binary is reached through current, so the swap never mutates the running image. All versions are retained, so any prior one is an instant offline flip.

kit install DIR [TOOL...] is the standalone link-layout tool for an existing binary. With no explicit tools it installs the default drop-in compiler/binutils and standard byte-utility names. kit install --all installs every public tool compiled into the public registry, while development tools such as emu are excluded. kit update uses the same link writer after installing or flipping a version, so $KIT_HOME/bin and an explicit kit install directory expose the same public tool names for the selected mode.

kit update                       # latest on the tracked channel (network)
kit update <file.kpkg>           # offline: verify + install a local package
kit update --version 2026.6.0    # offline flip if installed, else fetch
kit update --check               # report available vs installed; change nothing
kit update --rollback            # flip to the previous installed version
kit update --list                # list installed versions, mark current
kit update --prune               # remove all non-current installed versions
kit update --key <pubkey>        # verify against an explicit key (mirrors/forks)
kit update --allow-downgrade     # permit a channel-driven downgrade
kit update --dry-run             # print the plan; change nothing

Networked updates resolve the channel index from --index <url> or $KIT_UPDATE_INDEX_URL and download through a fetch hook in the driver (driver_fetch_url, which invokes curl directly with no downloader fallback); libkit itself stays network-free, and since packages are signed the transport is untrusted.

Version policy: channel-driven updates are monotonic by default — kit update refuses a version older than the running one. Deliberate moves bypass the guard: --version <X>, --rollback, local-file installs, and --allow-downgrade (all still fully verified). CalVer compare is numeric per field.

Self-update only ever writes under the user-writable $KIT_HOME; it never touches a system/root-owned install (e.g. under /usr/local) — there, re-acquire or use the OS package manager.

macOS Gatekeeper

A downloaded Mach-O is quarantined; current releases ship without Apple notarization, so remove the quarantine flag once:

$ xattr -dr com.apple.quarantine kit/bin/kit
$ kit/bin/kit --version

kit ad-hoc-signs its own Mach-O output, so the binary is valid code; this is only the download-quarantine prompt. Developer ID signing + notarization is a later milestone.