Bundle spec — what bundle.json is and how install.sh consumes it
A bundle is the per-customer, per-release configuration document
Seglamater ships to you alongside the installer. It pins exactly which
image digest your install pulls, on which channel you'll receive future
updates, and which cosign public key your updater trusts. The bundle is
small — a single JSON object — but it is the source of truth for
your install's identity from the moment you run install.sh.
The release-side authoring code lives in
crates/hivemind-release/src/bundle.rs. The consumer side lives in
scripts/install.sh (the validation block). When the two diverge,
new installs break; v0.1.0 shipped a manifest-shaped document where
the bundle should have been and AIWF's first install rejected it. The
v0.1.1+ shape documented here is the one install.sh validates.
Where the bundle comes from
You receive the bundle via your invite URL, the one-shot bootstrap endpoint your Seglamater contact emails you:
The bootstrap script:
- Fetches
bundle.json(this document) + a siblingbundle.json.sha256integrity hash froms.seglamater.app. - Writes both into your install dir under
./scripts/bundle.json. - Invokes
./scripts/install.sh --bundle ./scripts/bundle.jsonto render.env+ bring up the Compose stack with the pinned image.
You never edit bundle.json by hand. If anything inside it needs to
change (you're switching channels, a new release is out, your support
tier changed), you ask your Seglamater contact to issue a fresh invite
and re-run the bootstrap.
Lifecycle: when is a bundle minted?
- First install — Seglamater issues an invite scoped to your
organization. The bundle on the other side of that invite is pinned
to a specific image digest on your initial channel (usually
canaryfor new customers,stablefor established ones). - Major upgrade — when you opt in to a major version Seglamater may cut you a fresh bundle so the pinned image digest in your install dir matches the new line.
- Channel change — switching
canary→beta→stable(or back) is bundle-mediated. There is no in-app channel switcher today.
Day-to-day point releases do not require a new bundle. The
managed-update sidecar polls updates.seglamater.app on your bundle's
pinned channel + applies the new signed image digest in-place. See
upgrade.md.
Fields
The shape install.sh validates is schema_version: 1. The required
fields are the four at the top of install.sh's python validator
(line 240-257 in scripts/install.sh, kept in sync with the schema):
Required
| Field | Type | Validation | Why |
|---|---|---|---|
schema_version |
integer | MUST equal 1 |
Lets the installer fail fast on a v2 bundle it can't read. |
app |
string | MUST equal "hivemind" |
Prevents accidentally piping an atlas or vantage bundle here. |
version |
string | Free-form, conventionally semver | Stamped into .env as HIVEMIND_VERSION; surfaced at /api/v1/health. |
channel |
string | "canary", "beta", or "stable" |
What hivemind-updater polls. Bundle wins over the operator's --channel flag. |
Optional but always present in real bundles
| Field | Type | What |
|---|---|---|
created_at |
string | RFC-3339 timestamp the bundle was cut. Diagnostic. |
manifest_url |
string | Where the public release manifest lives — https://updates.seglamater.app/hivemind/releases/<version>/manifest.json. Used by cosign verify + by the updater poll. |
registry.image |
string | Floating-tag image ref, e.g. registry.seglamater.app/seglamater/hivemind-server:0.1.8. |
registry.digest |
string | sha256:<hex> — the pinned image digest. This is the tamper-evident pin. |
registry.ref_with_digest |
string | Convenience: <image>@<digest>. What install.sh actually pulls. |
cosign.pubkey_url |
string | Where to fetch the cosign public key (default https://updates.seglamater.app/.well-known/keys/hivemind.pub). |
cosign.pubkey_sha256 |
string | SHA-256 of the pubkey. install.sh fetches the key + asserts the hash matches before writing secrets/cosign_pub. A mismatch aborts install. |
support.email |
string | The mailbox for your install's tier — usually support@seglamater.com. |
support.docs_url |
string | Where the published customer docs live. |
files |
array of {path, role} |
What the bootstrap fetched alongside the bundle. Informational; install.sh doesn't re-fetch from this list. Common roles: compose, env-template, license. |
What's NOT in the bundle (intentional)
- No secrets. Database passwords, admin API keys, JWT keys, the
integration encryption key, cookie secrets — all of those are
generated locally by
install.shinto./secrets/on your host and never travel over the network. Seglamater never sees them. This is the security model: even a compromised bundle delivery channel cannot leak your install's keys. - No LLM API key. Hivemind is BYOK; you set
HIVEMIND_LLM_API_KEYin.envpost-install. Seebyok-llm.md. - No URL / hostname your install runs at.
HIVEMIND_PUBLIC_URLis set by you in.env; the bundle has nothing to say about your reverse-proxy hostname.
Integrity + signing
Three layers of integrity gate the install:
- Bundle SHA —
bundle.jsonis delivered alongside a siblingbundle.json.sha256.install.shreads both, recomputes the SHA-256 ofbundle.json, and aborts if it doesn't match. A missing.sha256is a warning, not a hard fail (some out-of-band delivery paths skip it), but the warning is loud. - Cosign pubkey pin — the cosign public key fetched from
pubkey_urlis hash-checked againstcosign.pubkey_sha256before being written tosecrets/cosign_pub. A mismatch aborts install immediately (no.tmpleft around to confuse retries). - Image digest pin —
install.shpullsregistry.ref_with_digest(theimage@sha256:…form). Docker verifies the manifest digest at pull time; substituting a different image on the registry can't slip through.
The bundle itself is also cosign-signed on the publisher side
(see dist/bundle.json.sig in the cut). v1 install.sh does not yet
verify that signature — it relies on the SHA-256 + the bootstrap
delivery channel. The bundle-cosign verification step is on the
roadmap; the artifacts are already published so the verification can
be turned on without a re-cut.
What install.sh actually does with each field
Tracing the consumer code: scripts/install.sh starts in argument
parsing, hits step 2 ("Validating bundle"), runs python against
bundle.json to validate + extract a small set of fields into shell
variables, then proceeds through env rendering + image pull.
| Bundle field | Effect on install |
|---|---|
app |
Asserted equal to "hivemind". |
schema_version |
Asserted equal to 1. |
version |
Written to .env as HIVEMIND_VERSION=<version>. Surfaced at /api/v1/health and used in the boot log version line. |
channel |
Written to .env as HIVEMIND_UPDATER_CHANNEL=<channel>. The updater sidecar polls that channel's manifest. |
registry.ref_with_digest |
docker pull <ref_with_digest> — the actual server image fetch. |
cosign.pubkey_url + cosign.pubkey_sha256 |
Fetched + hash-checked into secrets/cosign_pub. |
| Everything else | Available for diagnostic logging or future use; not load-bearing for the initial install. |
If a required field is missing, install.sh prints bundle missing
required fields: … and exits non-zero before touching docker. If
schema_version isn't 1 or app isn't hivemind, it prints the same
sort of error. The fail-closed posture is intentional: a bad bundle
should never produce a half-installed stack.
Error modes + what they mean
| Symptom | What's wrong | Fix |
|---|---|---|
install.sh: bundle.json parse failed: … |
The file isn't valid JSON. | Bootstrap pulled a corrupted file; re-run the invite. Don't hand-edit. |
install.sh: bundle missing required fields: … |
One of schema_version / app / version / channel is absent. |
Bundle is malformed — contact your Seglamater contact for a fresh invite. |
install.sh: bundle schema_version != 1 |
You're on an installer that pre-dates a v2 bundle (forward), or the bundle was issued for a different installer line. | Upgrade install.sh (re-run the invite) or ask for a v1 bundle. |
install.sh: bundle app != 'hivemind' |
You piped an atlas / vantage / other bundle through hivemind's installer. | Re-fetch the right invite. |
cosign public key SHA-256 mismatch |
The fetched pubkey doesn't match the pin in the bundle. Investigate before re-running. | This is either an in-flight MITM or a stale bundle against a key rotation. Don't override; contact security@seglamater.com. |
| Bundle SHA-256 mismatch warning | bundle.json was modified after bundle.json.sha256 was generated. |
Re-bootstrap from the invite. Don't edit the bundle. |
When to ask Seglamater for a fresh bundle
- You're switching channel and want it to stick.
- You believe the bundle was corrupted in delivery.
- Cosign pubkey rotation happened on Seglamater's side (we'll tell you, but if you see the SHA mismatch first the fix is a new bundle).
- A major version cut you opted in to that the v1 updater couldn't apply in-place (rare — most upgrades are point-release in-channel).
For routine point releases you do not need a fresh bundle — the sidecar pulls the new signed image on the channel your existing bundle already pins.
See also
bundle-schema.json— the formal JSON Schema (draft 2020-12) for programmatic validation in your tooling.bundle-example.json— a complete, realistic bundle you can use as a reference when reading your own.install.md— the install walkthrough; what happens before, during, and afterinstall.shconsumes the bundle.upgrade.md— what the channel + image digest in your bundle drive through the managed-update path.license.md— the license terms governing your use of the product the bundle gives you access to.