The Komet Checkpoint Pipeline

A checkpoint turns the entire Komet workspace — a dozen independently versioned repositories — into one reproducible, tagged snapshot, and a continuous-integration chain turns that snapshot into signed installers and a chat announcement. One command, ws:checkpoint-publish, starts it; everything after the git tag is automated.

This page explains the machinery end to end: the goal that cuts the checkpoint, the build that produces the .pkg and .msi installers, and the step that posts the result to Zulip. It is written for two readers — an operator who needs to cut a checkpoint, and an engineer who needs to understand or change how the pipeline works. Each stage closes with a Where the machinery lives note pointing into the source.

The three stages

The Komet checkpoint pipeline
Stage What happens Where it runs
1 · Cut ws:checkpoint-publish aligns versions, verifies the reactor builds, tags every subproject and the workspace aggregator at their current HEAD, writes a checkpoint manifest, and pushes. Operator’s machine, via the ws plugin (ike-platform).
2 · Build The pushed checkpoint/* tag triggers TeamCity, which builds native installers with JReleaser + jpackage, signs them, and notarizes the macOS package. CI (TeamCity project IkeKometWs).
3 · Announce The publish build creates a GitHub pre-release from both installers and posts an announcement, with a generated changelog, to Zulip. CI, via the notify-zulip.sh step (ike-ci).

Stage 1 — Cut the checkpoint

A checkpoint is a coordinated tag across the whole working set. Because Komet is assembled from many repositories, a single repo’s commit hash says nothing about whether the assembly builds. The checkpoint records the exact SHA, branch, and version of every member so the assembly is reproducible from one name.

The goal ships as a draft / publish pair — the same convention used throughout the ws plugin — so you can preview before you mutate:

ws:checkpoint-draft
Read-only preview. Runs the preflight checks and prints the tag it would create and the SHAs it would capture, writing nothing.
ws:checkpoint-publish
The mutation. It extends the draft behavior with four safeguards, in order:
  1. Require a clean working set — every subproject tree must match its committed HEAD, so the tag is reproducible.
  2. Auto-align — runs ws:align-publish to apply inter-subproject version alignment, committing any changes as a workspace: pre-checkpoint alignment commit.
  3. Verify the reactor — runs clean verify -DskipTests across the whole reactor and refuses to cut a checkpoint that does not build. (Skippable with -Dws.checkpoint.skipVerify=true when you have already built.)
  4. Tag and push — in topological order, tags each subproject checkpoint/<name>, writes the manifest to checkpoints/checkpoint-<name>.yaml, updates workspace.yaml with the captured SHAs, tags the aggregator, and pushes tags and branch to origin.

The checkpoint name defaults to <branch>-<yyyyMMdd>-<HHmmss> (UTC), e.g. main-20260622-030143. That timestamped name becomes the tag, the manifest filename, and the logical branch CI builds against.

Operator quickstart — from the workspace root (ike-komet-wsr), always through ws goals, never raw git:

# 1. Commit any work-in-progress (needs a Refs:/Fixes: issue trailer)
mvn ws:commit-publish -Dpush=false -Dmessage="..."

# 2. Push the commits
mvn ws:push

# 3. Preview the checkpoint
mvn ws:checkpoint-draft

# 4. Cut and push it (tag + manifest + push are atomic)
mvn ws:checkpoint-publish

checkpoint-publish tags and pushes in one step — there is no separate push toggle. Cutting from a freshly scaffolded clone needs care: the scaffold step leaves regenerable noise (wrapper files, CLAUDE.md edits) in each subproject tree, and the clean-tree preflight will refuse until that is stashed.

Where the machinery lives

Module ike-workspace-maven-plugin in ike-platform, package network.ike.plugin.ws:

  • WsCheckpointDraftMojo@Mojo(name = "checkpoint-draft"); the preflight, name derivation, and per-subproject tagging loop.
  • WsCheckpointPublishMojo@Mojo(name = "checkpoint-publish"); extends the draft with the clean-state, auto-align, reactor-verify, and push safeguards.
  • CheckpointSupport — the per-subproject tag-and-push engine, and the draft preview.
  • SubprojectSnapshot — the immutable (name, sha, branch, version, modified) record written into the checkpoint YAML.

Repository: IKE-Network/ike-platform[1].

Stage 2 — Build the installers

The pushed checkpoint/ tag is the trigger. TeamCity project *IkeKometWs discovers the tag and runs two installer builds — macOS and Windows — that each rebuild the tagged assembly into a native, signed package. Installers are date-versioned (e.g. 26.625.118-osx-aarch64), so no workspace SNAPSHOT qualifier ever reaches the shipped file.

The installer itself is assembled by JReleaser driving the JDK’s jlink and jpackage:

  • jlink produces a trimmed Java runtime image containing only the modules Komet needs, plus JVM options and resources.
  • jpackage wraps that image into a platform-native installer — .pkg on macOS, .msi on Windows, with .rpm/.dmg variants available.

On macOS the package must additionally pass Apple’s signing and notarization gauntlet, which is where the ike plugin’s installer goals come in. They run as Maven phases around the jpackage step:

Goal What it does
ike:jpackage-props Computes the ~19 derived properties JReleaser needs — build timestamp, platform triple, and the date-based app version (with the Windows minor ≤ 255 constraint handled separately).
ike:codesign-natives Signs every native library (.dylib/.jnilib), including those nested inside JARs (JNA, RocksDB), with the hardened-runtime entitlements Apple Silicon requires — without them the JVM crashes on launch.
ike:codesign-pkg Re-signs the .app bundle inside the .pkg with entitlements (a workaround for older JDKs; auto-skips on JDK 25.0.2+ where jpackage signs correctly).
ike:notarize Submits the package to Apple’s notary service, waits for acceptance, staples the ticket, and verifies it with spctl.

The same komet-desktop build runs locally in fast-dev mode by default — it produces only the jlink runtime image and skips installer, signing, and notarization. The full installer path activates with IKE_RELEASE=1 (the signed-installer profile), which is what CI sets.

A recurring non-code failure: notarization can return Apple HTTP 403 — "a required agreement is missing or has expired." That is the Apple Developer account’s license agreement needing re-acceptance, not a build problem.

Where the machinery lives

JReleaser config in komet-desktop (inside the ike-komet-wsr workspace):

  • src/main/jreleaser/jreleaser.yml — the jpackage assembler (.pkg/.msi/.rpm, signing, icons, resources).
  • src/main/jreleaser/jreleaser-jlink.yml — the jlink assemblers (runtime images, JVM options, file sets).
  • src/main/resources/installer/ — platform installer assets (entitlements plist, backgrounds, Windows icon).

Installer/signing goals in ike-maven-plugin (ike-tooling), package network.ike.plugin: JpackagePropsMojo, CodesignNativesMojo, CodesignPkgMojo, NotarizeMojo.

CI build configurations: TeamCity project IkeKometWs (InstallerMacOS, InstallerWindows).

Stage 3 — Announce to Zulip

When both installer builds are green, the publish build (IkeKometWs · Publish to GitHub) creates a GitHub pre-release and attaches both installers, then posts an announcement to Zulip so the team — and watchers — learn a fresh build is available.

The announcement is produced by notify-zulip.sh, the shared notifier used across IKE release pipelines. It:

  • resolves the target stream and finds-or-creates a topic, so a multi-step release threads under a single conversation rather than spamming one message per build;
  • posts terse progress as the chain advances, then a rich summary — a generated "what changed" changelog plus links to the release — on the final build;
  • is best-effort: a notification failure is logged but never fails the release.

The changelog body comes from the ike plugin’s release-notes machinery (ike:release-changelog / ReleaseNotesSupport), which derives entries from commit trailers and the GitHub milestone, so the human-readable "what changed" is generated, not hand-written.

Where the machinery lives
  • notify-zulip.sh — the notifier, embedded as the final build step of each release/publish configuration in ike-ci (.teamcity/).
  • IkeReleaseChangelogMojo (ike:release-changelog) and ReleaseNotesSupport in ike-tooling — generate the changelog the notification and the GitHub release share.

Stream, bot identity, and API key are TeamCity project parameters sourced from 1Password; no secret is committed. The operator-facing release and CI topology is documented in release-operations.adoc in ike-infrastructure.

Source repositories

Repository What it contributes
ike-platform[1] The ws:checkpoint-draft / ws:checkpoint-publish goals (Stage 1).
ike-tooling[2] The ike:* installer, signing, notarization, and changelog goals (Stages 2–3).
ike-komet-wsr[3] The Komet workspace and `komet-desktop’s JReleaser/installer config (Stage 2).
ike-ci[4] The TeamCity configuration and notify-zulip.sh (Stages 2–3).
Searching...
No results.