ADR-009: Independent Release Topology -- Core, Operator Image, Agent Image, and OCI Helm Charts Release on Their Own SemVer
Status: Accepted — partially superseded by ADR-010 (the agent image lane is retired; four lanes become three) Date: 2026-07-14 Authors: MCP Hangar Team
Context
The multi-repo split (see the vestigial packages/core/ cleanup, #466) left
MCP Hangar's shippable artifacts spread across several repositories, each with
its own release machinery:
mcp-hangar(Python core) --release-pleaseonmaincuts avX.Y.Ztag and GitHub release; the package publishes to PyPI. This is the one path that actually runs:v1.4.0is live, with a full tag history back tov1.2.1.mcp-hangar-operator(Go operator) --release.ymltriggers onpushof av*.*.*tag, builds a multi-arch image toghcr.io/mcp-hangar/mcp-hangar-operator, and attaches the rendered install manifest to the release.mcp-hangar-agent(Go sidecar) -- deployed in customer clusters via thehangar-agentchart. The agent image is a distinct artifact on its own SemVer, but its repository has no release workflow yet (see Consequences).helm-charts(three charts:mcp-hangar,mcp-hangar-operator,hangar-agent) --release-charts.ymltriggers onpushtomain(andworkflow_dispatch),helm pushes each chart tooci://ghcr.io/mcp-hangar/charts, and is idempotent: a chart whose version already exists in the registry is skipped. The version is whatever eachChart.yamldeclares -- there is no tag.docs-- carries a release hub / public install index and its ownrelease-please, publishing a docs package on av*.*.*tag.
The topology is implemented in CI but has never been exercised. A live
audit on 2026-07-14 confirms the earlier finding: mcp-hangar-operator,
helm-charts, and docs each have zero tags, zero GitHub releases, and no
successful release workflow run; the agent has no release workflow at all.
GHCR package visibility, ownership, retention, provenance, and signing could
not be verified with the available token. Only the Python core is proven
end-to-end.
Two things forced this decision now. First, #453 (compatibility matrix +
GHCR security policy) and the first-release verification issues
(mcp-hangar-operator#26, helm-charts#7) are all blocked on a ratified
statement of what the topology is. Second, the core repo still carries
dormant operator/Helm CI it should not remove until this ADR names the
independent topology as the intended end state.
The narrow question this ADR closes: do core, operator image, agent image, and charts release independently, each owning its own SemVer and its own artifacts -- or does a coordinating repo own a unified version? Everything else (compatibility ranges, GHCR hardening, first-release proof) is downstream of that answer and is delegated, not decided here.
Decision
-
Four independent release lanes, per-repo owners, independent SemVer lines. The Python core, the operator image, the agent image, and the Helm charts version and release independently. No repo owns a global "MCP Hangar version"; a deployment is a set of independently-versioned artifacts related by the compatibility matrix (
#453), not by a shared tag.Lane Owner repo Trigger (as decided) Artifact Core mcp-hangarrelease-please->vX.Y.ZtagPyPI package Operator image + manifest mcp-hangar-operatorpushtagv*.*.*multi-arch GHCR image + install manifest Agent image mcp-hangar-agentpushtagv*.*.*(workflow to be authored)multi-arch GHCR image OCI Helm charts helm-chartspushtomain, idempotent, perChart.yamlversion3 charts -> oci://ghcr.io/mcp-hangar/charts -
mcp-hangar-operatorowns the operator image and its install manifest. Av*.*.*tag is the single release trigger; it produces the multi-arch GHCR image and the rendered install manifest attached to that release. The operator's SemVer tracks the operator's own API/CRD surface, not the core. -
mcp-hangar-agentowns the agent image (fourth lane). The agent image releases independently on its own SemVer, tag-triggered, mirroring the operator lane. This lane is ratified but not yet built: the agent repo has norelease.ymltoday, so authoring it is an explicit follow-up. -
helm-chartsowns the OCI chart artifacts, and the asymmetry with the image lanes is deliberate. Charts publish tooci://ghcr.io/mcp-hangar/charts, one OCI tag perChart.yamlversion. The image lanes are tag-gated (a deliberate, human-cut event, appropriate for a distributed binary); the chart lane publishes idempotently on merge tomain(appropriate for a text artifact where theChart.yamlversion bump is the release intent). This asymmetry is ratified, not an accident. To make the chart lane safe, its CI must fail a chart change that does not bumpChart.yamlversion, so an un-bumped edit is a hard error rather than a silent idempotent skip. -
Chart
versionandappVersionare decoupled. Each chart'sversion(packaging SemVer) is independent of itsappVersion, andappVersionequals the released component image version the chart deploys. A chart-only fix bumpsversionwithout touchingappVersion; a new component image bumpsappVersion(and, per SemVer,version). Publication is idempotent -- re-running never overwrites an existing OCI tag. -
docsowns the public install/release index -- but only advertises what is verified. The release hub may list an install command (helm install ... oci://...,kubectl apply -f <manifest-url>,pip install mcp-hangar==X.Y.Z) only after the corresponding artifact has a verified published digest. Until then the index states the artifact is planned, not available. This is the async form of ADR-008's rule: do not advertise a capability that does not yet run. -
Release tags are immutable; consumers pin by digest. Once a component version is released it is never re-published or moved. Image and chart consumers pin by digest (
@sha256:...), consistent with ADR-004 (digest pinning). Mutable/rolling tags, if any exist, are conveniences layered on top of immutable digests, never the unit of trust. Verifying that GHCR actually enforces immutability and the rest of the security posture is delegated to#453.
Delegated to follow-ups (boundaries stated here, decided there)
- Compatibility matrix + CRD upgrade/rollback policy ->
#453. The ADR fixes the shape (core x operator x agent x chart x Kubernetes ranges, with an owner and an update procedure);#453fills and maintains it. Independent SemVer is only safe because a compatibility matrix exists -- shipping the matrix is a precondition of declaring independent release "supported." - GHCR security policy ->
#453. Target posture to be verified, not assumed: packages public; release tags immutable (Decision 7); keyless (OIDC)cosignsignatures + SBOM attestations attached; documented retention; consumer-side digest verification instructions. - First-release verification ->
mcp-hangar-operator#26,helm-charts#7, plus authoring the agent lane's release workflow. These cut and verify the first artifacts, producing the digests the docs index needs.
Ratified sub-decisions (2026-07-14)
The four sub-decisions the CI previously answered inconsistently were ratified by the maintainer and folded into the Decision above:
- Chart trigger asymmetry: kept (Decision 4), with a mandatory
Chart.yamlversion-bump CI guard added. appVersioncoupling: confirmed --appVersion= released component image version, chartversionindependent (Decision 5).- Agent as a fourth lane: confirmed (Decision 1 / 3).
- GHCR tag mutability: immutable release tags + digest-pinned consumption
confirmed as the intent for
#453to verify (Decision 7).
Rollout gate (decision is Accepted; rollout is staged)
The decision is ratified now. The rollout -- docs advertising install commands and core removing its dormant operator/Helm CI -- is staged behind:
-
mcp-hangar-operator#26: one verified operator image + manifest release, with a recorded digest. -
helm-charts#7: the three charts published to GHCR OCI, with recorded digests and a verifiedhelm pull. - Agent lane:
release.ymlauthored inmcp-hangar-agent, first image released and verified. -
#453: compatibility matrix published with an owner, and GHCR security posture verified against the live registry. -
docs: the release hub advertises only the verified artifacts.
Like the dormant governance in ADR-008, the topology is decided and asleep until these complete; nothing here claims it is operational today.
Consequences
- Independent cadence, at the cost of a matrix. Each component ships when it
is ready; a core patch does not gate an operator, agent, or chart release. The
price is that "which versions work together" stops being obvious from a shared
number and must be answered by the
#453matrix -- a real maintenance obligation, not a formality. - The agent lane is a known gap, now on the record. The
hangar-agentchart already ships (version/appVersion0.1.0) but the agent image has no release workflow. Ratifying it as an independent lane makes authoring that workflow a tracked follow-up rather than an oversight discovered at first release. - The image/chart trigger asymmetry is intentional and guarded. The
Chart.yamlversion-bump CI check (Decision 4) closes the one real hazard of the idempotent-push model -- a chart change that silently no-ops because its version was not bumped. - Dormant CI stays dormant a little longer. Core keeps its operator/Helm CI until the rollout gate is met, because removing it before the independent lanes are proven would strand the fallback.
- Docs can under-promise safely. Because the index is gated on verified digests, a half-built topology cannot leak an install command that 404s or pulls a non-existent chart -- the failure mode the audit was worried about.
- First release is a coordinated act, then never again. Bootstrapping the first operator, agent, and chart releases needs a maintainer with GHCR write and org context; steady-state releases afterward are per-repo and independent.
Alternatives Considered
- Unified monorepo version (one tag releases everything). Rejected: it
re-couples exactly what the multi-repo split decoupled, forces a core patch to
re-release the operator, agent, and charts, and makes "core
1.4.1with operator1.4.0" unexpressible -- the normal, healthy state of independent components. docs(or a hub repo) owns and orchestrates all releases centrally. Rejected: it puts release authority in the repo furthest from each artifact's build, and turns docs -- a consumer of verified digests -- into the producer, inverting the trust direction and coupling doc edits to artifact publication.- Force every lane onto one identical trigger. Rejected: a binary image and a text chart have genuinely different release semantics; imposing one mechanism on both trades a real fit for a cosmetic symmetry. The asymmetry is ratified explicitly (Decision 4) and made safe with the version-bump guard, rather than eliminated.
References
- Parent / ratification:
mcp-hangar/mcp-hangar#410(proposed ADR-009),mcp-hangar/docs#22(this document). - Follow-ups:
mcp-hangar-operator#26(first image/manifest release),mcp-hangar/helm-charts#7(OCI chart policy + first release),mcp-hangar/mcp-hangar#453(compatibility matrix + GHCR security policy), agent-lanerelease.yml(to be filed againstmcp-hangar-agent). - Related: ADR-004 (digest pinning; the trust unit for released artifacts), ADR-008 (do-not-advertise-what-does-not-run, and decided-but-dormant framing).
- Cleanup context:
mcp-hangar/mcp-hangar#466(remove vestigialpackages/core/). - Workflows audited (2026-07-14):
mcp-hangar-operator/.github/workflows/release.yml,helm-charts/.github/workflows/release.yml,docs/.github/workflows/publish.yml;mcp-hangar-agenthas no release workflow.