diff --git a/modules/meshstack/composition/buildingblock/README.md b/modules/meshstack/composition/buildingblock/README.md
new file mode 100644
index 00000000..6c67ff62
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/README.md
@@ -0,0 +1,96 @@
+---
+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 and a building block from it.
+# 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 that
+definition.
+
+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 meshObjects are named after `link_name` (default `Link`) to keep them distinguishable
+from the `Composition Demo` block that created them.
+
+## 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 both meshObjects are created with that key, meshStack records it as their creation author and
+surfaces **"created by building block"** provenance on the definition and the building block, 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 |
+
+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.
+
+
+## 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 |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [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 |
+| [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. |
+| [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..e35a573b
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/outputs.tf
@@ -0,0 +1,23 @@
+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 "summary" {
+ value = chomp(<<-EOT
+ Created a Link building block definition and a building block from it. Both show this building
+ block as their creator.
+
+ | meshObject | Name | UUID |
+ |---|---|---|
+ | Building Block Definition | ${var.link_name} | `${meshstack_building_block_definition.created.metadata.uuid}` |
+ | Building Block | ${var.link_name} | `${meshstack_building_block.created.metadata.uuid}` |
+ EOT
+ )
+ description = "Markdown summary shown on the building block's detail page."
+}
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..885df83e
--- /dev/null
+++ b/modules/meshstack/composition/buildingblock/variables.tf
@@ -0,0 +1,23 @@
+# 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."
+}
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..e14c33f9
--- /dev/null
+++ b/modules/meshstack/composition/meshstack_integration.tf
@@ -0,0 +1,183 @@
+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 "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 and a building block from it 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 a link building block definition in your workspace and a building block from it. Both
+ 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 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 definition and
+ building block 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 name and link target | ❌ | ✅ |
+ | 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)
+ }
+ 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"
+ }
+ 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",
+ ]
+ }
+}
+
+terraform {
+ required_version = ">= 1.12.0"
+
+ required_providers {
+ meshstack = {
+ source = "meshcloud/meshstack"
+ version = ">= 0.24.0"
+ }
+ }
+}