diff --git a/modules/meshstack/composition/buildingblock/README.md b/modules/meshstack/composition/buildingblock/README.md
new file mode 100644
index 00000000..3c6bd9ad
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/README.md
@@ -0,0 +1,122 @@
+---
+name: Composition Demo
+supportedPlatforms:
+ - meshstack
+description: |
+ Reference building block demonstrating the composition pattern: it uses the run's
+ ephemeral API key to create a building block definition with a building block from it,
+ and an empty platform with a landing zone.
+# Creates only meshObjects through the meshStack API, so there is nothing to set up cloud-side.
+requiresBackplane: false
+---
+# Composition Demo
+
+This building block is a reference implementation of the **composition** pattern: a building block
+that provisions other meshObjects through the meshStack API instead of cloud resources. It creates:
+
+- a `meshBuildingBlockDefinition` in the consuming workspace and a `meshBuildingBlock` from it, and
+- an **empty platform** — a `meshPlatformType`, a `meshLocation`, a `meshPlatform` and a
+ `meshLandingZone` on it.
+
+The created definition runs the hub's [`link`](../../link) building block, which provisions nothing
+but a `terraform_data` and needs neither a cloud provider nor an operator. Reusing it keeps the whole
+chain automatic and avoids inventing a throwaway implementation just to have something to create.
+Both created building block objects are named after `link_name` (default `Link`) to keep them
+distinguishable from the `Composition Demo` block that created them.
+
+*Empty* is the point of the platform: it is a `CUSTOM` platform with no cloud connection config and a
+landing zone whose platform properties are `{}`. meshStack ships no `CUSTOM` platform type and every
+built-in type is a cloud platform whose config would need real credentials, so the module creates a
+type of its own. Nothing about it can be replicated to a cloud — it exists to carry a name, a landing
+zone and its creation provenance.
+
+## How it works
+
+The building block definition declares `permissions`, so meshStack issues an **ephemeral API key**
+scoped to the consuming workspace for the duration of each run and injects it as `MESHSTACK_ENDPOINT`
+/ `MESHSTACK_API_TOKEN`. The `meshstack` provider picks those up with no explicit configuration, so
+`provider "meshstack" {}` is all this module needs.
+
+Because every meshObject is created with that key, meshStack records it as their creation author and
+surfaces **"created by building block"** provenance on each of them, linking back to the composition
+that created them. That makes this module a convenient end-to-end check of that provenance without any
+cloud platform involved.
+
+## Permissions
+
+| Permission | Why |
+|---|---|
+| `BUILDINGBLOCKDEFINITION_LIST` / `_SAVE` / `_DELETE` | Manage the created building block definition |
+| `BUILDINGBLOCK_LIST` / `_SAVE` / `_DELETE` | Manage the created building block |
+| `PLATFORMINSTANCE_LIST` / `_SAVE` / `_DELETE` | Manage the created platform, its location and its platform type |
+| `LANDINGZONE_LIST` / `_SAVE` / `_DELETE` | Manage the created landing zone |
+
+The created definition itself declares no permissions, so it stays within meshStack's privilege
+escalation guard (a nested definition may only request a subset of its parent's permissions).
+
+## Notes
+
+- This module does not wait for the created building block's run. That run needs a terraform runner,
+ and a stack with a single one cannot start it before this run returns. Provenance is recorded when
+ the block is created, so nothing here depends on the result.
+- For the same reason the created definition uses `deletion_mode = "PURGE"`. `DELETE` would schedule
+ a deprovisioning run on teardown that cannot start until the composition's own destroy run returns.
+ Purging leaks nothing, since `link` provisions no infrastructure.
+- `hub_git_ref` is wired in as a static input from the composition's own `var.hub.git_ref`, so the
+ created definition clones the `link` module from the same hub revision.
+- The created definition's version stays a **draft**. Releasing needs admin approval, which the run's
+ ephemeral key — a plain workspace key — cannot obtain, so `draft = false` would leave the version
+ `DRAFT` regardless, warn on every run, and leave `version_latest_release` null. The created
+ building block therefore references `version_latest`, which a draft permits because the definition
+ and the building block's target are the same workspace.
+- A platform, location and platform type identifier is globally unique and cannot be reused once
+ deleted. The module therefore derives all three from `building_block_uuid`, a
+ `TENANT_BUILDING_BLOCK_UUID` input carrying the UUID of the building block the run belongs to, so
+ each building block gets identifiers of its own that survive re-runs.
+- The created platform is `PRIVATE` and `UNPUBLISHED`, which keeps it out of the marketplace.
+ meshStack ties those two together — `UNPUBLISHED` is rejected for any other restriction — and
+ `PRIVATE` in turn requires `restricted_to_workspaces` to name exactly the owning workspace.
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [meshstack](#requirement\_meshstack) | >= 0.24.0 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [meshstack_building_block.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/building_block) | resource |
+| [meshstack_building_block_definition.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/building_block_definition) | resource |
+| [meshstack_landingzone.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/landingzone) | resource |
+| [meshstack_location.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/location) | resource |
+| [meshstack_platform.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/platform) | resource |
+| [meshstack_platform_type.created](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/resources/platform_type) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [building\_block\_uuid](#input\_building\_block\_uuid) | UUID of the building block this run belongs to. Wired in as a TENANT\_BUILDING\_BLOCK\_UUID input and used to name the created platform, location and platform type, whose identifiers must be globally unique. | `string` | n/a | yes |
+| [hub\_git\_ref](#input\_hub\_git\_ref) | Hub reference the created building block definition clones its implementation from. Wired in as a static input from the composition's own `var.hub.git_ref`, so both definitions stay on the same hub revision. | `string` | n/a | yes |
+| [link\_name](#input\_link\_name) | Name given to the building block definition and building block this composition creates. | `string` | `"Link"` | no |
+| [link\_url](#input\_link\_url) | Target of the link the created building block publishes. | `string` | n/a | yes |
+| [platform\_name](#input\_platform\_name) | Display name of the platform this composition creates. Its identifier is generated instead of taken from here, because a platform identifier cannot be reused once deleted. | `string` | `"Composition Demo Platform"` | no |
+| [workspace\_identifier](#input\_workspace\_identifier) | Workspace the created building block definition is owned by and the created building block is attached to. Wired in as a WORKSPACE\_IDENTIFIER input, so it is always the consuming workspace — the same one the run's ephemeral API key is scoped to. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [created\_building\_block\_definition\_uuid](#output\_created\_building\_block\_definition\_uuid) | UUID of the building block definition this composition created. |
+| [created\_building\_block\_uuid](#output\_created\_building\_block\_uuid) | UUID of the building block this composition created. |
+| [created\_landing\_zone\_identifier](#output\_created\_landing\_zone\_identifier) | Identifier of the landing zone this composition created. Landing zones have no UUID in the API. |
+| [created\_platform\_uuid](#output\_created\_platform\_uuid) | UUID of the platform this composition created. |
+| [summary](#output\_summary) | Markdown summary shown on the building block's detail page. |
+
diff --git a/modules/meshstack/composition/buildingblock/logo.png b/modules/meshstack/composition/buildingblock/logo.png
new file mode 100644
index 00000000..634d5dcb
Binary files /dev/null and b/modules/meshstack/composition/buildingblock/logo.png differ
diff --git a/modules/meshstack/composition/buildingblock/main.tf b/modules/meshstack/composition/buildingblock/main.tf
new file mode 100644
index 00000000..6a4a4da3
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/main.tf
@@ -0,0 +1,118 @@
+# Both resources below are created through the meshStack API with the run's ephemeral API key, which
+# meshStack records as their creation author. That is what makes them show "created by building
+# block" provenance pointing back at the building block this module runs for.
+
+# Runs the hub's `link` building block rather than an implementation of its own: `link` provisions
+# nothing but a terraform_data, needs no cloud provider and no operator, which makes it the cheapest
+# real implementation to hand a created definition. See modules/meshstack/link.
+resource "meshstack_building_block_definition" "created" {
+ metadata = {
+ owned_by_workspace = var.workspace_identifier
+ }
+
+ spec = {
+ display_name = var.link_name
+ description = "Link building block definition created by the Composition Demo building block."
+ target_type = "WORKSPACE_LEVEL"
+ run_transparency = true
+
+ readme = chomp(<<-EOT
+ A link building block definition created by a composition rather than by a platform engineer.
+
+ Ordering it publishes a link in your workspace and provisions no infrastructure.
+ EOT
+ )
+ }
+
+ version_spec = {
+ # Stays a draft: releasing a version needs admin approval, which the run's ephemeral key — a
+ # plain workspace key — cannot obtain. Setting draft = false would leave the version DRAFT
+ # anyway, warn on every run, and leave version_latest_release null. The building block below can
+ # still be created from a draft because the definition and the target are the same workspace,
+ # which satisfies BuildingBlockCreationValidator.requireAccess's `selfOwning` branch.
+ draft = true
+
+ # PURGE, not DELETE: DELETE would schedule a deprovisioning run for the building block below when
+ # this composition is torn down, and that run cannot start until the composition's own destroy run
+ # returns — a deadlock wherever the terraform runner pool has a single worker. Nothing is leaked
+ # by purging, because the `link` implementation provisions nothing but a terraform_data.
+ deletion_mode = "PURGE"
+
+ implementation = {
+ terraform = {
+ repository_url = "https://github.com/meshcloud/meshstack-hub.git"
+ repository_path = "modules/meshstack/link/buildingblock"
+ ref_name = var.hub_git_ref
+ terraform_version = "1.11.0"
+ async = false
+ use_mesh_http_backend_fallback = true
+ }
+ }
+
+ # All static, so the created building block below has no inputs left to supply. Mirrors the
+ # wiring in modules/meshstack/link/meshstack_integration.tf.
+ inputs = {
+ url = {
+ display_name = "URL"
+ description = "Target of the link."
+ type = "STRING"
+ assignment_type = "STATIC"
+ argument = jsonencode(var.link_url)
+ }
+ title = {
+ display_name = "Title"
+ description = "Human-readable name of the linked resource."
+ type = "STRING"
+ assignment_type = "STATIC"
+ argument = jsonencode(var.link_name)
+ }
+ # Empty falls back to the summary the link module generates from title and url.
+ summary = {
+ display_name = "Summary"
+ description = "Markdown rendered for the application team after deployment."
+ type = "STRING"
+ assignment_type = "STATIC"
+ argument = jsonencode("")
+ }
+ }
+
+ outputs = {
+ url = {
+ display_name = var.link_name
+ type = "STRING"
+ assignment_type = "RESOURCE_URL"
+ }
+ summary = {
+ display_name = "Summary"
+ type = "STRING"
+ assignment_type = "SUMMARY"
+ }
+ }
+ }
+}
+
+resource "meshstack_building_block" "created" {
+ spec = {
+ # version_latest, not version_latest_release: the definition above stays a draft, so
+ # version_latest_release is null.
+ building_block_definition_version_ref = {
+ uuid = meshstack_building_block_definition.created.version_latest.uuid
+ content_hash = meshstack_building_block_definition.created.version_latest.content_hash
+ }
+
+ display_name = var.link_name
+
+ target_ref = {
+ kind = "meshWorkspace"
+ name = var.workspace_identifier
+ }
+
+ # Every input of the definition above is static, so there is nothing for this block to set.
+ inputs = {}
+ }
+
+ # Deliberately not waiting: this run would be waiting on a run of the same implementation type,
+ # which a stack with a single terraform runner cannot start until this one returns. Nothing below
+ # depends on the result either — provenance is recorded when the block is created.
+ wait_for_completion = false
+}
diff --git a/modules/meshstack/composition/buildingblock/outputs.tf b/modules/meshstack/composition/buildingblock/outputs.tf
new file mode 100644
index 00000000..76c5e39f
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/outputs.tf
@@ -0,0 +1,37 @@
+output "created_building_block_definition_uuid" {
+ value = meshstack_building_block_definition.created.metadata.uuid
+ description = "UUID of the building block definition this composition created."
+}
+
+output "created_building_block_uuid" {
+ value = meshstack_building_block.created.metadata.uuid
+ description = "UUID of the building block this composition created."
+}
+
+output "created_platform_uuid" {
+ value = meshstack_platform.created.metadata.uuid
+ description = "UUID of the platform this composition created."
+}
+
+output "created_landing_zone_identifier" {
+ value = meshstack_landingzone.created.metadata.name
+ description = "Identifier of the landing zone this composition created. Landing zones have no UUID in the API."
+}
+
+output "summary" {
+ value = chomp(<<-EOT
+ Created a Link building block definition with a building block from it, and an empty platform with a
+ landing zone on it. All of them show this building block as their creator.
+
+ | meshObject | Name | Identifier |
+ |---|---|---|
+ | Building Block Definition | ${var.link_name} | `${meshstack_building_block_definition.created.metadata.uuid}` |
+ | Building Block | ${var.link_name} | `${meshstack_building_block.created.metadata.uuid}` |
+ | Platform Type | ${meshstack_platform_type.created.spec.display_name} | `${meshstack_platform_type.created.metadata.name}` |
+ | Location | ${meshstack_location.created.spec.display_name} | `${meshstack_location.created.metadata.name}` |
+ | Platform | ${var.platform_name} | `${meshstack_platform.created.identifier}` |
+ | Landing Zone | ${meshstack_landingzone.created.spec.display_name} | `${meshstack_landingzone.created.metadata.name}` |
+ EOT
+ )
+ description = "Markdown summary shown on the building block's detail page."
+}
diff --git a/modules/meshstack/composition/buildingblock/platform.tf b/modules/meshstack/composition/buildingblock/platform.tf
new file mode 100644
index 00000000..d215e38b
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/platform.tf
@@ -0,0 +1,105 @@
+# The second thing this composition creates: an *empty* platform with a landing zone on it. Empty means
+# the least a platform can carry — a CUSTOM platform type, no cloud connection config, and a landing
+# zone whose platform properties are `{}`. That is enough for meshStack to record and show creation
+# provenance on both, and it needs no cloud credentials and no operator.
+#
+# As in main.tf, everything here is created with the run's ephemeral API key, which is what makes
+# meshStack attribute the creation to the building block this module runs for.
+
+locals {
+ # A platform, location and platform type identifier is globally unique and cannot be reused once
+ # deleted, so the objects below are named after the building block that owns them. Unlike a random
+ # suffix this survives re-runs without a stored value, and it makes the identifiers point back at
+ # their creator on sight.
+ suffix = substr(replace(var.building_block_uuid, "-", ""), 0, 8)
+ identifier = "composition-demo-${local.suffix}"
+}
+
+# meshStack ships no CUSTOM platform type, and every built-in type is a cloud platform whose config
+# would need real credentials — so an empty platform needs a type of its own.
+resource "meshstack_platform_type" "created" {
+ metadata = {
+ # Platform type identifiers are uppercase, unlike every other identifier here.
+ name = upper(local.identifier)
+ owned_by_workspace = var.workspace_identifier
+ }
+
+ spec = {
+ # Carries the suffix because meshStack requires platform type display names to be globally unique.
+ display_name = "${var.platform_name} ${upper(local.suffix)}"
+
+ # A 1x1 transparent PNG. The API requires an icon, and nothing here is meant to be looked at.
+ icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII="
+ }
+}
+
+resource "meshstack_location" "created" {
+ metadata = {
+ name = local.identifier
+ owned_by_workspace = var.workspace_identifier
+ }
+
+ spec = {
+ display_name = "${var.platform_name} Location"
+ description = "Location created by the Composition Demo building block."
+ }
+}
+
+resource "meshstack_platform" "created" {
+ metadata = {
+ name = local.identifier
+ owned_by_workspace = var.workspace_identifier
+ }
+
+ spec = {
+ display_name = var.platform_name
+ description = "Empty platform created by the Composition Demo building block."
+ endpoint = "https://docs.meshcloud.io"
+
+ location_ref = {
+ name = meshstack_location.created.metadata.name
+ }
+
+ # Keeps the platform out of the marketplace, which is where an empty platform belongs. meshStack
+ # ties the three fields together: UNPUBLISHED is only allowed with PRIVATE, and PRIVATE requires
+ # `restricted_to_workspaces` to name exactly the owner.
+ availability = {
+ restriction = "PRIVATE"
+ publication_state = "UNPUBLISHED"
+ restricted_to_workspaces = [var.workspace_identifier]
+ }
+
+ config = {
+ custom = {
+ platform_type_ref = {
+ name = meshstack_platform_type.created.metadata.name
+ }
+ }
+ }
+ }
+}
+
+resource "meshstack_landingzone" "created" {
+ metadata = {
+ # Only unique per platform, so it can repeat the platform's identifier.
+ name = local.identifier
+ owned_by_workspace = var.workspace_identifier
+ }
+
+ spec = {
+ display_name = "${var.platform_name} Landing Zone"
+ description = "Empty landing zone created by the Composition Demo building block."
+
+ automate_deletion_approval = false
+ automate_deletion_replication = false
+
+ platform_ref = {
+ uuid = meshstack_platform.created.metadata.uuid
+ }
+
+ # A custom platform has no landing zone properties at all.
+ platform_properties = {
+ custom = {}
+ }
+ }
+}
diff --git a/modules/meshstack/composition/buildingblock/provider.tf b/modules/meshstack/composition/buildingblock/provider.tf
new file mode 100644
index 00000000..e03d77b1
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/provider.tf
@@ -0,0 +1,4 @@
+# Authenticates from the MESHSTACK_ENDPOINT / MESHSTACK_API_TOKEN environment variables the runner
+# injects for the run's ephemeral API key.
+provider "meshstack" {
+}
diff --git a/modules/meshstack/composition/buildingblock/variables.tf b/modules/meshstack/composition/buildingblock/variables.tf
new file mode 100644
index 00000000..382f5f18
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/variables.tf
@@ -0,0 +1,34 @@
+# Defaulted, unlike the other inputs, so that a run can still destroy a building block whose stored
+# inputs predate a rename of this one. A destroy only has to delete the created meshObjects, and their
+# display names have no bearing on that.
+variable "link_name" {
+ type = string
+ default = "Link"
+ description = "Name given to the building block definition and building block this composition creates."
+}
+
+variable "link_url" {
+ type = string
+ description = "Target of the link the created building block publishes."
+}
+
+variable "workspace_identifier" {
+ type = string
+ description = "Workspace the created building block definition is owned by and the created building block is attached to. Wired in as a WORKSPACE_IDENTIFIER input, so it is always the consuming workspace — the same one the run's ephemeral API key is scoped to."
+}
+
+variable "hub_git_ref" {
+ type = string
+ description = "Hub reference the created building block definition clones its implementation from. Wired in as a static input from the composition's own `var.hub.git_ref`, so both definitions stay on the same hub revision."
+}
+
+variable "platform_name" {
+ type = string
+ default = "Composition Demo Platform"
+ description = "Display name of the platform this composition creates. Its identifier is generated instead of taken from here, because a platform identifier cannot be reused once deleted."
+}
+
+variable "building_block_uuid" {
+ type = string
+ description = "UUID of the building block this run belongs to. Wired in as a TENANT_BUILDING_BLOCK_UUID input and used to name the created platform, location and platform type, whose identifiers must be globally unique."
+}
diff --git a/modules/meshstack/composition/buildingblock/versions.tf b/modules/meshstack/composition/buildingblock/versions.tf
new file mode 100644
index 00000000..da8747cc
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/versions.tf
@@ -0,0 +1,8 @@
+terraform {
+ required_providers {
+ meshstack = {
+ source = "meshcloud/meshstack"
+ version = ">= 0.24.0"
+ }
+ }
+}
diff --git a/modules/meshstack/composition/meshstack_integration.tf b/modules/meshstack/composition/meshstack_integration.tf
new file mode 100644
index 00000000..b973113c
--- /dev/null
+++ b/modules/meshstack/composition/meshstack_integration.tf
@@ -0,0 +1,221 @@
+variable "link_name" {
+ type = string
+ default = "Link"
+ description = "Name given to the building block definition and building block this composition creates. Both are the hub's `link` building block, hence the default."
+}
+
+variable "link_url" {
+ type = string
+ default = "https://docs.meshcloud.io"
+ description = "Target of the link the created building block publishes. The created definition runs the hub's `link` building block, which provisions nothing but this address."
+}
+
+variable "platform_name" {
+ type = string
+ default = "Composition Demo Platform"
+ description = "Display name of the empty platform this composition creates. Its identifier is generated from the building block UUID, because a platform identifier cannot be reused once deleted."
+}
+
+variable "meshstack" {
+ type = object({
+ owning_workspace_identifier = string
+ tags = optional(map(list(string)), {})
+ })
+ description = "Shared meshStack context. Tags are optional and propagated to building block definition metadata."
+}
+
+variable "hub" {
+ type = object({
+ git_ref = optional(string, "main")
+ bbd_draft = optional(bool, true)
+ })
+ const = true
+ default = {
+ git_ref = "main"
+ bbd_draft = true
+ }
+ description = <<-EOT
+ `git_ref`: Hub release reference. Set to a tag (e.g. 'v1.2.3') or branch or commit sha of the meshstack-hub repo.
+ `bbd_draft`: If true, the building block definition version is kept in draft mode.
+ EOT
+}
+
+output "building_block_definition" {
+ description = "BBD is consumed in building block compositions."
+ value = {
+ uuid = meshstack_building_block_definition.this.metadata.uuid
+ version_ref = var.hub.bbd_draft ? meshstack_building_block_definition.this.version_latest : meshstack_building_block_definition.this.version_latest_release
+ git_ref = var.hub.git_ref
+ }
+}
+
+resource "meshstack_building_block_definition" "this" {
+ metadata = {
+ owned_by_workspace = var.meshstack.owning_workspace_identifier
+ tags = var.meshstack.tags
+ }
+
+ spec = {
+ display_name = "Composition Demo"
+ description = "Reference building block demonstrating the composition pattern: creates a Link building block definition with a building block from it, plus an empty platform with a landing zone, all using the run's ephemeral API key."
+ target_type = "WORKSPACE_LEVEL"
+ symbol = "https://raw.githubusercontent.com/meshcloud/meshstack-hub/${var.hub.git_ref}/modules/meshstack/composition/buildingblock/logo.png"
+
+ # Lets consuming workspace users read the run logs, which is where the interesting part happens.
+ run_transparency = true
+
+ readme = chomp(<<-EOT
+ Creates four meshObjects in your workspace: a link building block definition with a building block
+ from it, and an empty platform with a landing zone on it. All of them carry a "created by building
+ block" reference back to this building block, so you can follow where they came from. Nothing is
+ provisioned outside meshStack, and no step needs an operator.
+
+ ## 🎯 When to use it
+
+ Use this building block when you:
+ - Want to see the composition pattern end to end — a building block provisioning meshObjects
+ through the meshStack API rather than cloud resources.
+ - Need a self-service way to hand a workspace its own building block definition or platform
+ without a platform engineer creating one by hand.
+
+ ## 💡 Usage examples
+
+ **Example 1: Exploring compositions**
+ A developer adds this building block to a sandbox workspace and inspects the meshObjects that
+ appear, following the creator link on each back to this building block.
+
+ **Example 2: Offering a workspace its own marketplace link**
+ A team wants a documentation link published in their own workspace's marketplace and gets both
+ the definition and a ready-made building block by adding this building block.
+
+ ## 📊 Shared Responsibility
+
+ | Responsibility | Platform Team | Application Team |
+ |---|:---:|:---:|
+ | Maintain this composition and its permissions | ✅ | ❌ |
+ | Operate whatever the published link points at | ❌ | ❌ |
+ | Choose the names, the link target and the platform name | ❌ | ✅ |
+ | Decide when to add or remove this building block | ❌ | ✅ |
+ EOT
+ )
+ }
+
+ version_spec = {
+ draft = var.hub.bbd_draft
+
+ # DELETE so removing the building block runs `tofu destroy` and cleans up what it created.
+ deletion_mode = "DELETE"
+
+ implementation = {
+ terraform = {
+ repository_url = "https://github.com/meshcloud/meshstack-hub.git"
+ repository_path = "modules/meshstack/composition/buildingblock"
+ ref_name = var.hub.git_ref
+ terraform_version = "1.11.0"
+ async = false
+ use_mesh_http_backend_fallback = true
+ }
+ }
+
+ inputs = {
+ link_name = {
+ assignment_type = "USER_INPUT"
+ type = "STRING"
+ display_name = "Link Name"
+ description = "Name given to the building block definition and building block this composition creates."
+ default_value = jsonencode(var.link_name)
+ }
+ link_url = {
+ assignment_type = "USER_INPUT"
+ type = "STRING"
+ display_name = "Link URL"
+ description = "Target of the link the created building block publishes."
+ default_value = jsonencode(var.link_url)
+ }
+ platform_name = {
+ assignment_type = "USER_INPUT"
+ type = "STRING"
+ display_name = "Platform Name"
+ description = "Display name of the empty platform this composition creates."
+ default_value = jsonencode(var.platform_name)
+ }
+ # Names the created platform, location and platform type, whose identifiers are globally unique.
+ building_block_uuid = {
+ assignment_type = "TENANT_BUILDING_BLOCK_UUID"
+ type = "STRING"
+ display_name = "Building Block UUID"
+ description = "UUID of this building block, used to name the created platform and its location and type."
+ }
+ workspace_identifier = {
+ assignment_type = "WORKSPACE_IDENTIFIER"
+ type = "STRING"
+ display_name = "Workspace Identifier"
+ description = "Workspace the created meshObjects belong to. Always the consuming workspace, which is the one the run's ephemeral API key is scoped to."
+ }
+ # Pins the created definition's implementation to the same hub revision as this one.
+ hub_git_ref = {
+ assignment_type = "STATIC"
+ type = "STRING"
+ display_name = "Hub Git Ref"
+ argument = jsonencode(var.hub.git_ref)
+ }
+ }
+
+ outputs = {
+ created_building_block_definition_uuid = {
+ assignment_type = "NONE"
+ type = "STRING"
+ display_name = "Created Building Block Definition"
+ }
+ created_building_block_uuid = {
+ assignment_type = "NONE"
+ type = "STRING"
+ display_name = "Created Building Block"
+ }
+ created_platform_uuid = {
+ assignment_type = "NONE"
+ type = "STRING"
+ display_name = "Created Platform"
+ }
+ created_landing_zone_identifier = {
+ assignment_type = "NONE"
+ type = "STRING"
+ display_name = "Created Landing Zone"
+ }
+ summary = {
+ assignment_type = "SUMMARY"
+ type = "STRING"
+ display_name = "Summary"
+ }
+ }
+
+ # Grants each run an ephemeral API key with exactly these workspace permissions. Creating the
+ # meshObjects with that key is what records this building block as their creator.
+ permissions = [
+ "BUILDINGBLOCKDEFINITION_LIST",
+ "BUILDINGBLOCKDEFINITION_SAVE",
+ "BUILDINGBLOCKDEFINITION_DELETE",
+ "BUILDINGBLOCK_LIST",
+ "BUILDINGBLOCK_SAVE",
+ "BUILDINGBLOCK_DELETE",
+ # Platform instance rights also cover locations and platform types.
+ "PLATFORMINSTANCE_LIST",
+ "PLATFORMINSTANCE_SAVE",
+ "PLATFORMINSTANCE_DELETE",
+ "LANDINGZONE_LIST",
+ "LANDINGZONE_SAVE",
+ "LANDINGZONE_DELETE",
+ ]
+ }
+}
+
+terraform {
+ required_version = ">= 1.12.0"
+
+ required_providers {
+ meshstack = {
+ source = "meshcloud/meshstack"
+ version = ">= 0.24.0"
+ }
+ }
+}