The platform-curated catalog for Musher — the one-click deployable items that appear in the storefront.
Each item is a self-contained directory under items/ holding the three
authoring surfaces a catalog entry spans: its components (the building
blocks), its blueprint (the composition graph), and its listing (the
storefront wrapper). All three use the Musher spec-document envelope, defined
normatively in
musher-dev/specifications.
Third-party marks. The icons under
items/*/media/are the official marks of the upstream projects they identify. They remain the property of their respective owners, are included for nominative identification only, and are not covered by this repository'sLICENSE. SeeNOTICEandICONS.md.
items/
└── <slug>/ # ONE self-contained item per directory
├── listing.yaml # storefront wrapper
├── blueprint.yaml # composition graph; absent for a COMPONENT item
├── components/
│ └── <name>.yaml # ≥1; every file referenced by blueprint.yaml
└── media/ # optional assets
├── icon.png
└── screenshots/*.png
The directory name is the slug, so slug uniqueness is structural.
These are hard requirements. A violation is rejected when the platform syncs
this repo — though not all of them are caught before that: the ones marked
(sync only) are capability-phase obligations that npm test cannot see,
because an offline validator is forbidden to report them.
- the directory name equals both
listing.yaml's andblueprint.yaml'smetadata.slug; - the listing's
spec.itemTypeisBLUEPRINTexactly when the item holds ablueprint.yaml, andCOMPONENTotherwise; - every blueprint node's
componentRefresolves to acomponents/<name>.yamlfile in the same item directory, and every such file is referenced — no unreferenced components; - media paths are item-relative, live under
media/, contain no.., use a supported extension, and exist on disk; - every node whose component runs carries
compute.profile, naming a Compute Profile the platform offers — and a node deploying anEXTERNALcomponent, which runs nothing, carries nocomputeat all; - every volume a component declares is allocated a
sizeGiBon the node, at or above the component's ownminSizeGiB; - no two inputs claim one
envVarKey: a workload's environment is exactly its inputs' targets, and nothing else writes it; - no authored literal supplies a sensitive contract, from anywhere: a
sensitiveinput carries nodefault, no node binds a literalvalueto one, and no parameter bound to one carries adefaulteither. Such a value is supplied at installation, or generated; - volume mount paths are canonical, and no two are the same or nested;
- a
JOB'sschedule.cronis five numeric fields, each within its range; - no value depends on itself through
{node, output}bindings; - component shape follows
spec.type: aWORKERmay declare private endpoints but is never exposed, and aJOBdeclares none. APUBLICHTTP endpoint needs a readiness probe; - an exposure is bare
PUBLIC/PRIVATEor the object form{ visibility, access, viewerIdentity }.access: AUTHENTICATEDneeds anHTTP,HTTPSorWSendpoint, and a component readingviewerIdentityHeaderortrustedProxyCIDRsneeds its node to forward identity there withviewerIdentity: HEADER; - an
HTTPSendpoint whose certificate nothing can verify says so withtls: { verify: NONE }, and a trust bundle or probe credential reads an input that is always supplied. A probe password or token is never a literal; - a runnable component carries a
workload, a workload carries asource, aSERVICEdeclares at least one endpoint, aJOBcarries acommand, anEXTERNALcomponent publishes at least one output, and every input and output is described. A blueprint node deploying a component checks these offline; for aCOMPONENTitem, which no node deploys, they are (sync only).
Per the spec, a BLUEPRINT item deploys exactly one blueprint, and compute is a
per-node concern on the blueprint node rather than on the component. The
blueprint's metadata.revision is the item's revision; a listing carries none.
Component and blueprint documents each carry a required metadata.description:
plain text, at most 280 characters, describing the thing itself. The listing's
summary remains the storefront copy.
Create items/<slug>/ with a component file per building block.
components/my-app.yaml
specVersion: v1
kind: COMPONENT
metadata:
revision: 1 # the revision this document is released at
description: What this component is, in a sentence. # REQUIRED
spec:
type: SERVICE # SERVICE | WORKER | JOB | EXTERNAL
workload: # required unless EXTERNAL; forbidden on it
source:
image: ghcr.io/example/my-app:1.2.3 # a bare name means :latest, as in Docker
endpoints:
primary:
targetPort: 8080 # the port the process listens on
protocol: HTTP
health:
readiness:
http: { endpoint: primary, path: /healthz } # the endpoint is named
initialDelaySeconds: 30
volumes:
data:
mountPath: /var/lib/my-app
minSizeGiB: 5 # the floor; the blueprint allocates the size
contract: # what the component needs, never where it comes from
inputs:
adminPassword:
description: Password for the bootstrap admin account. # REQUIRED
schema: { type: string } # lowercase JSON Schema types
sensitive: true # beside the schema, not inside it
required: true
target: { envVarKey: ADMIN_PASSWORD }
dataDir: # a constant is an input with a default
description: Directory my-app keeps its data files in.
schema: { type: string }
default: /var/lib/my-app
target: { envVarKey: DATA_DIR }
outputs: {}The key that is present says where a value comes from. A workload source is
{image} or {git: …}; an output's from is {value}, {input},
{endpoint, property} or {template}; a node binding is {parameter},
{node, output} or {value}. A type appears only where the variant is a
category — spec.type, schema.type — and a field naming another key in the
same document is a bare noun: input, endpoint, parameter, node,
output. Only componentRef, which points at another document, takes a suffix.
blueprint.yaml — references the component file by repo-local path, binds
compute, storage and exposure per node, wires every input explicitly, and
authors the install form:
specVersion: v1
kind: BLUEPRINT
metadata:
slug: my-app # equals the directory name
revision: 1 # the item's revision
description: What this deployment is, in a sentence. # REQUIRED
spec:
components:
web: # graph-local node name
componentRef: ./components/my-app.yaml # must begin ./ and end .yaml
compute:
profile: general.standard.small
volumes:
data: { sizeGiB: 5 } # at or above the component's minSizeGiB
exposure:
primary: PUBLIC # an endpoint left out is PRIVATE
bindings: # one entry per input this node takes a value for
adminPassword: { parameter: adminPassword }
parameters: # everything the installation takes from outside
adminPassword:
generator: { byteLength: 32, encoding: HEX } # HEX | BASE64 | BASE64URL
ui: { label: Admin password }Nothing binds by name. A parameter reaches an input because some node's
bindings says so, which is what makes adding an unrelated node safe. A
parameter carries ui plus at most one of default, generator and from,
and states no schema, no required and no description: the input it is
bound to declares all three, and the form field reads them from there. An
absent or empty parameters is a form with no fields, which is right only when
every required input is bound to something else or already carries a default.
A parameter default is a literal — it interpolates nothing, and being one it
can never supply a sensitive input; that field is filled at installation or by
a generator. A value from
outside the documents arrives through from, which is exactly one whole
reference in one of two namespaces: ${{ variables.cloud.region }} names one
organization variable, and ${{ connections.llm.default }} names an atomic
connection. A node's own allocated address is not a parameter source; the
component publishes it as an output and the node binds it back:
# components/my-app.yaml
outputs:
publicURL:
description: Public URL this deployment answers at.
schema: { type: string }
from: { endpoint: primary, property: publicURL }
# blueprint.yaml
bindings:
siteURL: { node: web, output: publicURL }A node reading its own endpoint output is a discovery dependency and not a value cycle: the address is allocated before anything runs.
The ./ prefix is load-bearing, not decorative: a bare name is not
distinguishable from the UUID a published reference uses, so without it no
validator could tell which resolver the reference wanted.
listing.yaml
specVersion: v1
kind: LISTING
metadata: { slug: my-app } # slug only — a listing carries no revision
spec:
itemType: BLUEPRINT # BLUEPRINT iff the item holds blueprint.yaml
displayName: My App
summary: One-line storefront tagline (≤ 280 chars)
description: |
Markdown long-form description.
category: DEVELOPER_TOOLS
lifecycleStage: STABLE # STABLE | BETA | EXPERIMENTAL | SUNSET
tags: [example]
homepageURL: https://example.com
sourceRepoURL: https://github.com/example/my-app
license: MIT # SPDX expression; LicenseRef-… when SPDX has none
icon: media/icon.png # optional; see ICONS.md
screenshots: # optional; {file, caption?} in display order
- file: media/screenshots/01-home.png
caption: The home screenA multi-service item adds more entries under spec.components — unique node
names, one components/<name>.yaml per reference — and binds a consumer's input
to a producer's output with {node, output}. The two ends agree on
schema.type, with one widening: an integer output satisfies a number input.
Input, output, parameter and connection names are lowerCamelCase, and an
acronym keeps its conventional case (baseURL, publicURL, homepageURL) —
the environment-variable key is what target.envVarKey carries, not the input's
name.
A node the platform does not run — a service addressed elsewhere, such as a
managed database — is a component declaring spec.type: EXTERNAL. It has no
workload, its inputs carry no target, its outputs are non-empty, and its
blueprint node carries no compute.
A language model is one connection, never three values — and the connection
enters through a node, not a field. A component declaring spec.type: EXTERNAL
takes the connection whole on a connection input, and publishes its three
members as outputs; the workload that calls the model declares three ordinary
string inputs, and the blueprint wires them together:
# components/llm.yaml — the node that supplies the model
spec:
type: EXTERNAL
contract:
inputs:
llm:
description: Language-model connection this node stands for.
connection:
protocol: OPENAI_CHAT_COMPLETIONS # or ANTHROPIC_MESSAGES
capabilities: [STREAMING]
outputs:
baseURL:
description: Base URL of the API.
schema: { type: string }
from: { input: llm, member: baseURL }
apiKey:
description: Credential for the API above.
schema: { type: string }
sensitive: true # the member is secret, so this is
from: { input: llm, member: apiKey }
model:
description: Model the API answers with.
schema: { type: string }
from: { input: llm, member: model }
# blueprint.yaml
parameters:
llm:
from: "${{ connections.llm.default }}"
ui: { label: Language model }
components:
llm:
componentRef: ./components/llm.yaml # EXTERNAL, so no compute
bindings:
llm: { parameter: llm }
web:
componentRef: ./components/my-app.yaml
compute: { profile: general.standard.small }
bindings:
llmBaseURL: { node: llm, output: baseURL }
llmAPIKey: { node: llm, output: apiKey }
llmModel: { node: llm, output: model }A connection input declares only description and connection, is always
required, and is the one kind of input only an EXTERNAL component may
declare. A connection parameter binds to a connection input and to nothing else,
and a connection input takes no other kind of binding. So one connection
parameter fills one external node, and its endpoint, credential and model always
come from one selection — wiring apiKey from one node and baseURL from
another is possible, but it has to be written down.
A workload never asks for a protocol. It sees three strings, and the node that supplies them declares what it requires, which is the same shape a node already uses to read a managed database's host and port.
An item holding no blueprint.yaml is an itemType: COMPONENT item: a
single building block rather than a composition — postgres and redis, which
wrap a workload, and llm-endpoint, the EXTERNAL component a language model
enters through. Listing spec §3 binds the two together, so a COMPONENT item
cannot carry a blueprint and a
BLUEPRINT item cannot omit one. Such an item has no item revision; its
component documents carry their own. A blueprint that needs one of these
building blocks carries its own copy under components/, because a repo-local
reference cannot leave its item directory.
npm install
npm testEvery item is validated against an exact release of each family of
musher-dev/specifications:
core/v1.0.0, listing/v1.0.0, component/v1.5.0 and blueprint/v1.6.0.
Families release independently, so they sit at different numbers — component and
blueprint are past 1.0.0 because
ADR 0033
made inputs the only way into a component, which was breaking for both. Nothing is vendored. The schemas are fetched from their exact release
URLs at specifications.musher.dev, and the release's conformance corpus from
its GitHub release assets. Every byte is checked against the digest the release
records, so the corpus is judged against exactly the contract it names, and
adopting a newer release is a deliberate change of one version and its
digests. The origins are
public, so no credential is involved.
The suite covers the three phases a client can decide offline: the YAML
profile, the JSON Schema bundles, and the semantic rules that bind an item's
documents to each other and to its directory. It also runs the specification's
own conformance cases through those phases, so a rule here that disagrees with
the specification fails by case id. See tests/README.md. For
what each field means, read the generated
field reference rather than a
copy of it.
The Musher platform remains the sole authority. These tests are the same
contracts applied early, not a second one: they run the phases that need no
network, and they cannot see the capability phase at all — whether a Compute
Profile is actually offered, whether a published component exists, whether a
version is monotonic, and, for a COMPONENT item, the runtime minimums listed
as (sync only) above. An item that passes here can still be rejected at
sync.
Keep changes to one item per pull request, so a rejection that only the platform can raise is easy to attribute. Adopting a new specification release is the standing exception: a release that narrows what validates makes every unmigrated item fail at once, so the pins and the documents move together.
This repository holds the platform-curated catalog — the items Musher
maintains directly. It is public so the corpus is a browsable worked example
for anyone authoring against
musher-dev/specifications.
Community-authored catalog items are not submitted here. They are created and managed directly on the Musher platform. Pull requests adding new third-party listings to this repository will be closed with a pointer to that flow.
Corrections to existing items — a stale image tag, a broken link, an upstream rebrand, a clearer summary — are welcome as pull requests.
The YAML and prose in this repository are covered by LICENSE.
The third-party marks under items/*/media/ are not — see
NOTICE.