Module version management
Manage Module Versions in the Orchestrator through the Console, API, octl
or Terraform/OpenTofu.
Use compatible server, Runner and client versions from the Module Management release. Clients that predate this release cannot publish managed Module Versions or supply the required output declarations.
Identities and definitions
A Module has an immutable UUID, an organisation-unique technical ID and one permanent Resource Type binding. Its display name, description and tags are catalogue metadata. Changing that metadata does not publish another version.
Every Module Version has its own immutable UUID and a complete executable definition. Parameters, fixed inputs, dependencies, provider mappings, source, declared outputs and optional release notes belong to that version. Publishing a new version requires a complete definition; missing fields are never inherited from the Default. Cloning helps you write that definition without creating an inheritance relationship.
Use canonical Semantic Versioning, such as 1.2.3 or 1.3.0-rc.1, without a
leading v. The complete value, including build metadata, is unique within a
Module. Build metadata distinguishes identities but does not increase version
precedence. Once a Default exists, each new version must have higher precedence
than that Default. If the Default pointer is empty, it must have higher
precedence than the most recently Defective Default. You may publish higher
candidates out of order.
Resource Types are immutable interfaces. Every publication must conform to its Module’s Resource Type. A changed Resource Type needs a new identity and a new Module binding. Conformance validates the declared graph interface; it does not guarantee that a cloud provider accepts every runtime configuration. The authoring contract explains required inputs, parameters, providers and graph dependencies. A new publication must explicitly declare the exact output schema of a constrained Resource Type; Stellwerk does not infer that claim from source code.
Release lifecycle
| Status | Meaning | Normal deployment behavior |
|---|---|---|
| Proposed | The one candidate under evaluation | Exact explicit selection only |
| Default | The organisation’s implicit selection | Resolved when a deployment plan is created |
| Deprecated | Withdrawn candidate or replaced Default | No new adoption; an active Pin can preserve an already-active version |
| Defective | Known faulty or unsafe, terminal | Blocked, except separately authorised exact-version safety exceptions |
You can create an empty Module, but cannot deploy it. Every published version starts Proposed, and a Module can have at most one Proposed version at a time. Proposed versions do not expire or change status with time. Release notes are optional and immutable.
Promoting a stable Proposed version atomically makes it Default and marks the former Default Deprecated. This changes future resolution, not running infrastructure. Check adoption before promotion: later deployments cannot reuse a Deprecated version unless an exact active Environment Pin permits it.
A prerelease can be Proposed and explicitly deployed, but never Default. Publish stable successor atomically deprecates the exact expected prerelease and creates a separate, higher-precedence stable Proposed version. If any validation fails, neither side changes. Promote the stable successor explicitly. See Release a stable version from a prerelease for CLI and Terraform/OpenTofu examples.
Marking the current Default Defective clears the Default pointer. Stellwerk does not silently choose a replacement or change existing Environments. Restore can select only the exact immediately preceding Default, provided it remains Deprecated. It cannot skip further back in history. When that predecessor is unavailable or Defective, publish a corrected version instead.
Every transition after publication needs a reason. Lifecycle commands use an
Idempotency Key and the observed resource_version, so network retries do not
duplicate a command and concurrent changes cannot silently overwrite decisions.
After a conflict, read the current state and review it before submitting a new
command with a new key.
Publish and promote with octl
Configure octl for your installation and organisation and authenticate first.
These commands require organisation-scoped Module publication and promotion
permissions. The example uses inline source, which needs no artifact digest
and creates no infrastructure by itself.
Create a Resource Type and an empty catalogue entry:
octl create resource-type release-marker \
--set-json='{"output_schema":{"type":"object","properties":{"value":{"type":"string"}}}}'
octl create module-catalogue-entry \
--set-json='{"id":"release-marker","resource_type":"release-marker","display_name":"Release Marker"}'
Save the complete publication body as release-marker-1.0.0.yaml:
semantic_version: 1.0.0
module_source: inline
module_source_code: |
output "value" {
value = "release-one"
}
module_params: {}
module_inputs: {}
provider_mapping: {}
dependencies: {}
coprovisioned: []
output_schema:
type: object
properties:
value:
type: string
release_notes: Initial release marker.
Publish it and read the lifecycle revision:
octl create module-version release-marker \
--set-yaml=@release-marker-1.0.0.yaml \
--idempotency-key=release-marker-publish-1.0.0
octl get module-version release-marker 1.0.0 -o json
The response contains version.uuid, version.lifecycle_status and
version.resource_version. Set VERSION_REVISION to the returned revision,
then promote:
octl update module-version release-marker 1.0.0 \
--action=promote --expected-version="$VERSION_REVISION" \
--reason="Make the reviewed initial version available" \
--idempotency-key=release-marker-promote-1.0.0
A Module Rule and a normal deployment manifest are still needed to use the Module in an Environment. Follow First deployment for the complete Runner and application path.
Release a stable version from a prerelease
You cannot rename 1.1.0-rc.2 to 1.1.0, even while it is Proposed. Publish a
stable successor instead. This creates a new Version UUID and deprecates the
prerelease in one operation. The stable version starts Proposed; promotion to
Default is a separate decision. Neither operation deploys infrastructure.
The examples continue with the release-marker Module and Resource Type above.
Use either the CLI or Terraform/OpenTofu to publish the successor, not both for
the same release.
With octl
First publish a candidate using the complete definition from the earlier example:
octl create module-version release-marker \
--set-yaml=@release-marker-1.0.0.yaml \
--set=semantic_version=1.1.0-rc.2 \
--set='release_notes=Release candidate for 1.1.0.' \
--idempotency-key=release-marker-publish-1.1.0-rc.2
If your candidate already exists, skip that command. Read its current revision.
The following commands use jq to extract it:
PRERELEASE_REVISION=$(octl get module-version release-marker 1.1.0-rc.2 \
-o json | jq -r '.version.resource_version')
Save the successor request as release-marker-stable-1.1.0.yaml. The version
object is a complete publication definition, including semantic_version.
Copy the candidate’s reviewed definition and set its stable version and release
notes. Fields you omit are not inherited:
reason: Candidate accepted for the stable release.
version:
semantic_version: 1.1.0
module_source: inline
module_source_code: |
output "value" {
value = "release-one"
}
module_params: {}
module_inputs: {}
provider_mapping: {}
dependencies: {}
coprovisioned: []
output_schema:
type: object
properties:
value:
type: string
release_notes: Stable release after candidate review.
Submit the request, supplying the revision you just read:
octl create stable-module-version release-marker 1.1.0-rc.2 \
--set-yaml=@release-marker-stable-1.1.0.yaml \
--set="expected_prerelease_resource_version=$PRERELEASE_REVISION" \
--idempotency-key=release-marker-graduate-1.1.0-rc.2
On success, 1.1.0-rc.2 is Deprecated and the new 1.1.0 is Proposed. The
response contains both lifecycle records and their shared correlation_id.
A validation or concurrency failure leaves both sides unchanged. Keep the
same request and Idempotency Key for a network retry; after a conflict, read
and review the current state before preparing another command.
When you are ready to make 1.1.0 the Default, read its revision and promote it:
STABLE_REVISION=$(octl get module-version release-marker 1.1.0 \
-o json | jq -r '.version.resource_version')
octl update module-version release-marker 1.1.0 \
--action=promote --expected-version="$STABLE_REVISION" \
--reason="Make the reviewed stable release the Default" \
--idempotency-key=release-marker-promote-1.1.0
With Terraform or OpenTofu
With the stellwerk-labs/platform-orchestrator provider configured, use
platform-orchestrator_stable_module_version_successor for the same atomic
operation. This example assumes 1.1.0-rc.2 is already Proposed. Set
prerelease_revision to its reviewed resource_version, obtained with the
read command above. The provider’s definition contains the complete stable
publication body, not the outer CLI request:
variable "prerelease_revision" {
type = number
description = "Reviewed prerelease resource_version before graduation."
}
resource "platform-orchestrator_stable_module_version_successor" "marker_1_1_0" {
module_id = "release-marker"
prerelease_version_id = "1.1.0-rc.2"
expected_prerelease_resource_version = var.prerelease_revision
reason = "Candidate accepted for the stable release."
definition = jsonencode({
semantic_version = "1.1.0"
module_source = "inline"
module_source_code = "output \"value\" { value = \"release-one\" }"
module_params = {}
module_inputs = {}
provider_mapping = {}
dependencies = {}
coprovisioned = []
output_schema = {
type = "object"
properties = { value = { type = "string" } }
}
release_notes = "Stable release after candidate review."
})
}
Review and apply the plan:
tofu plan -var="prerelease_revision=$PRERELEASE_REVISION" -out=graduate.tfplan
tofu apply graduate.tfplan
Use terraform in place of tofu for Terraform. The resource records the
operation’s id, stable_version_uuid and prerelease_version_uuid. Do not
also declare a new platform-orchestrator_module_version resource to publish
1.1.0; the successor resource has already created it.
To manage promotion through Terraform/OpenTofu too, read the new stable
version’s resource_version, review it, then add this separate operation:
variable "stable_revision" {
type = number
description = "Reviewed stable resource_version before promotion."
}
resource "platform-orchestrator_module_version_lifecycle_transaction" "promote_marker_1_1_0" {
transaction = jsonencode({
transitions = [{
module_id = "release-marker"
module_version_id = platform-orchestrator_stable_module_version_successor.marker_1_1_0.stable_version_uuid
action = "promote"
expected_resource_version = var.stable_revision
reason = "Make the reviewed stable release the Default"
}]
})
}
STABLE_REVISION=$(octl get module-version release-marker 1.1.0 \
-o json | jq -r '.version.resource_version')
tofu plan -var="prerelease_revision=$PRERELEASE_REVISION" \
-var="stable_revision=$STABLE_REVISION" -out=promote.tfplan
tofu apply promote.tfplan
tofu plan -var="prerelease_revision=$PRERELEASE_REVISION" \
-var="stable_revision=$STABLE_REVISION"
Keep those reviewed revision values unchanged after each successful operation,
for example in your release’s variable file. Do not wire a live data source’s
revision into either receipt: the operation changes that revision, which would
make a later plan try to replace the receipt. The final plan should show no
changes. If a version resource also manages the prerelease, leave its
lifecycle_status omitted so it observes the graduation instead of trying to
restore Proposed. See Terraform/OpenTofu ownership transfer.
Both resources record completed operations. Removing them from Terraform state does not undo graduation or promotion, delete either Version, or free its Semantic Version for reuse.
External source and verification
An external Terraform/OpenTofu source requires an exact source_revision.
For Git, pin the same immutable commit in the source URL’s ref; a mutable
branch or tag alone is insufficient. An external artifact_digest is optional.
When supplied, use sha256: followed by 64 lowercase hexadecimal characters.
Omit an unknown digest, do not submit an empty string or generate a dummy value.
Inline source must always omit it.
A digest is the publisher’s immutable claim about the external executable artifact. It does not cover the complete Module Version definition. The source revision selects the source; neither it nor a digest proves that the content has been verified. Versions remain Unverified whether or not a digest is supplied. This release has no trusted artifact verifier or agreed canonical artifact serialization shared by all clients. Unverified does not block publication, explicit use or promotion. An absent digest cannot be filled into a published version later; a changed definition or additional immutable metadata requires a new version. Do not interpret a stored digest, a declared schema or a successful deployment as proof of independent content verification. Retain external sources needed by deployments and recovery in your own storage.
Inspect history, comparison and adoption
Open Modules, select the Module, then open Versions. Inspect the immutable definition and release notes, lifecycle events, usage and Pins before acting. Select two versions for comparison: the response shows concrete before/after values for changed inputs, parameters, source, dependencies, mappings and outputs. It is an informational structural diff, not a promise of application compatibility.
For example, a change to a fixed input is shown as an actual value change:
module_inputs:
- replicas: 2
+ replicas: 3
The summary identifies changed fields. Typed before / after snapshots show
their exact definitions, including changes to output_schema. A missing
historical declaration differs from an explicitly declared {}. Unchanged
fields can be omitted from the display. A comparison does not create a
deployment, approve a version or prove that every Environment can apply it.
The equivalent CLI reads are:
octl get module-versions release-marker --include-deprecated --include-defective
octl get module-version-history release-marker 1.0.0
octl get module-version-usage release-marker 1.0.0
octl get module-version-comparison release-marker 1.0.0 1.1.0
Module catalogue access and Version history access are separate permissions.
module.read lets a user discover Modules. module.version.read is required to
read Version details, lifecycle history, usage and before/after comparisons. If
history is unavailable because that permission is missing, treat it as an
authorization issue, not as proof that the Module is empty or has no history.
Both versions must exist for a comparison. Adoption reflects the latest trustworthy successful Deployment in each Environment. Check the observation time and any unknown-state indicators. If trustworthy state is unavailable, you cannot conclude that the Module is unused. History links point to the versions and deployments that actually ran, even after the Default changes.
Archive and retain
Archive hides a Module from the default catalogue view and blocks publication, promotion and new adoption. It never destroys infrastructure. Existing Environments may carry their exact active version forward subject to lifecycle and Pin rules. Defective marking remains available as a safety action. Unarchive restores catalogue availability with a reason and an audit event.
Published versions, SemVer identities and lifecycle history are permanent. Only an unused empty Module shell without history or references can be deleted. An archived Module keeps its technical ID reserved. See Pins and migration and retention for operational workflows.