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

StatusMeaningNormal deployment behavior
ProposedThe one candidate under evaluationExact explicit selection only
DefaultThe organisation’s implicit selectionResolved when a deployment plan is created
DeprecatedWithdrawn candidate or replaced DefaultNo new adoption; an active Pin can preserve an already-active version
DefectiveKnown faulty or unsafe, terminalBlocked, 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.

Top