Reference / blueprint / v1
Status: Stable
Family: blueprint
Schema: https://specifications.musher.dev/blueprint/v1/blueprint.schema.json
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 RFC 2119 and RFC 8174 when, and only when, they appear in all capitals, as shown here.
What is normative. This document, together with the Musher Document Core Specification it applies, defines the complete behaviour of the specification. The JSON Schema bundle is its executable form for structural validity, and the conformance corpus is its executable form for observable outcomes; both are normative, and neither is permitted to disagree with this document or with the other. Schema
descriptionfields, examples, generated documentation, and validator message text are informative.A disagreement between two normative artifacts is a defect in this specification and blocks a release. Until it is fixed this document governs — but that is how to read a broken contract, not a licence for the schema to be wrong.
A Blueprint Document composes one or more Component Documents into a single deployable application: which components participate, what compute each runs on, how they are wired to one another, and what the installation takes from outside the documents.
The blueprint is the unit of deployment.
Out of scope for this document
component familylisting familyapiVersion: musher.dev/v1, kind: App shape read by musher deploy.
ADR 0001 §2
declines to ratify it. It is superseded by a repo-local blueprint document
paired with its component documents, which the reference form in
§4.1 makes expressible.specVersion: v1
kind: BLUEPRINT
metadata: { slug: …, revision: …, description: … }
spec: { parameters: {…}, components: {…} }
A Blueprint Document is a Musher document as the Musher Document Core Specification defines one, and every rule of core v1 applies to it. This family binds the parameters core v1 §1.1 leaves to a family:
| Core parameter | This family |
|---|---|
kind (CORE-ENV-002) | BLUEPRINT |
metadata (CORE-ENV-003) | §3 |
Fields accepting null (CORE-ENV-007) | spec.parameters.*.default; spec.components.*.bindings.*.value and descendants permitted by receiving schemas (§4.2) |
| Item document (core v1 §4.1) | blueprint.yaml |
This specification narrows core v1 where it says so and relaxes it nowhere. It cites core by its line ("core v1 §N"), and the core edition a release was built and tested against is recorded with that release (core v1 §9).
Normative dependencies
The compatibility guarantee of
core v1 §3 runs, for this family, from
v1.3.0:
ADR 0033
§5 withdrew v1.0.0 to v1.2.0 from it, once, before anyone outside the
project had adopted them.
metadata carries slug, revision and description, and nothing else. A
record identifier and a concurrency token, the id and rowVersion an API
carries on a declarative apply, describe a row in a control plane, not a
document. They MUST NOT appear on a blueprint document, and a validator MUST
reject them with ERR_UNKNOWN_FIELD like any other unknown property.
BP-ID-004: description says
what the deployable application is, in one or two sentences of plain text, 1 to
280 characters, the limit of a
listing summary. A consumer MUST NOT
render it as Markdown. An empty description, or one longer than 280 characters,
is ERR_INVALID_VALUE at /metadata/description. These are structural rules,
and they bind a description that is present. Whether one has to be present is
BP-ID-005.
BP-ID-005: A published blueprint MUST carry a
description. One absent at publication is rejected in the capability phase
with ERR_DESCRIPTION_REQUIRED at
/metadata. The reasoning is
component §4's and is not restated:
an unwritten description is a blueprint someone is still writing, and only a
publisher can tell that from one that is finished.
The description is for the people who install and operate the application. It
is not storefront copy: that is the sibling listing's summary, and neither is
derived from the other.
revision counts releases of the catalog item. It is an integer, 1 or
greater, REQUIRED and never defaulted. Like a component's
(component §4) it names a position in one
lineage and nothing more: nothing is derivable from the distance between 2 and
7, and nothing is promised about how one revision of an item behaves against
another. It orders, and that is the whole of its job.
It is the item's only revision. A blueprint is the one item document that carries one: listing §3 says why the sibling listing carries none, and component §4 says why the component documents beneath the item count something else. An item holding no blueprint therefore has no item revision at all, and is ordered by the components it publishes.
Two rules bind the item together. Both are semantic, and both are measured
against the item root §3.1 locates. The first is stated once
for every family in
core v1 §4.2:
CORE-ITEM-001 binds metadata.slug to
the item directory name. The second is this family's:
| ID | Rule | Diagnostic |
|---|---|---|
BP-ID-003 | Every component document in the item MUST be referenced by some node. | ERR_UNREFERENCED_COMPONENT |
An unreferenced component document is an error, not dead weight. A
component nothing references is not deployed, not checked against any node,
and not visible to a reader working out what the item contains. Permitted, it
accumulates: last release's postgres.yaml sitting beside the one actually in
use, with nothing in the directory saying which is live. The diagnostic
anchors at /spec/components, the mapping that should have named the file,
because a JSON Pointer addresses this document, and the file it is complaining
about is not in it.
What v1 does not constrain. An item holding a blueprint.yaml and no
listing.yaml is not rejected. Nothing in
core v1 §4.2 requires the sibling listing
§3.1 shows, and an item with no storefront entry is one that
is deployable and not browsable rather than one that is malformed. Nothing
orders one item's revisions against another's either, and nothing checks a
blueprint revision against the lineage it extends: minimum: 1 is the whole of
the offline rule. Component §4 carries a capability rule for a component's
lineage (COMP-ID-001), and this
family has no analogue of it. This family's capability rules are BP-ID-005,
about the description, and BP-GRAPH-001, about the graph.
Neither is about the lineage.
A blueprint and its sibling listing are the item documents of one catalog item.
<slug>/
blueprint.yaml this document
listing.yaml the sibling storefront entry
components/ the component documents the graph references
media/ icon and screenshots
blueprint.yaml is this family's item document, so the directory containing it
is the item root core v1 §4.1
defines. It is what §3's rules are measured against, what
§4.1 means by containment, and what
listing §5 resolves a media path inside.
Only two names in that tree are fixed: blueprint.yaml and listing.yaml.
Component documents MAY sit anywhere under the root: components/ is a
convention, and §4.1 accepts a flat sibling equally.
media/ is fixed too, but by the listing family rather than by this one.
A blueprint handed over with no directory has no item root, so core v1 §4.1 forbids reporting any of §3's item rules for it. §4.1 draws the same line for the component reference, for the same reason.
Nodes live in spec.components, keyed by a core label. Each node requires
componentRef, and a node whose component runs also requires compute
(§4.3). components itself is REQUIRED, and structurally it
MAY be empty.
BP-GRAPH-001: A published blueprint MUST declare
at least one node. An empty components at publication is rejected in the
capability phase with ERR_NODE_REQUIRED at /spec/components. The
reasoning is component §4's and is
not restated: an empty graph is a blueprint someone is still writing, and only a
publisher can tell that from one that is finished. An offline validator
therefore MUST NOT report ERR_NODE_REQUIRED.
components:
web:
componentRef: ./components/web.yaml
compute:
profile: general.standard.small
placement: { cpuDedication: dedicated }
volumes:
uploads: { sizeGiB: 20, storageClass: local-nvme }
exposure: { web: PUBLIC }
bindings:
databaseURL: { node: db, output: connectionString }
siteTitle: { parameter: siteTitle }
| Member | Holds | Section |
|---|---|---|
componentRef, revision | The component document the node deploys | §4.1 |
bindings | The supplier of each input | §4.2 |
compute | The compute profile and its placement pins | §4.3, §4.4 |
volumes | The storage allocated to each volume | §4.3 |
exposure | The exposure of each endpoint, and who may reach it | §4.3, §4.5 |
componentRef takes one of two forms:
./ or ../ and ends .yaml or .yml. It
resolves relative to this blueprint, and its real path, including symlinks,
MUST remain in the item root. It takes its revision from the component
document and forbids revision on the node.revision.BP-REF-002: Local and published contracts undergo the
same structural and semantic checks. Acquisition is separate: the semantic
evaluator MUST NOT fetch dependencies. Supplied published contracts are keyed by
identity and revision, with the document's bytes and their verified SHA-256
digest. Missing context yields INCOMPLETE; a missing local file, malformed
supplied contract or digest mismatch is INVALID. Every rule that reads the
component a node deploys is deferred, not passed, while that component is
unavailable.
BP-REF-003: The component a node deploys MUST meet
component's publication obligations
(component §5), whether it
is published or repo-local. A component may be stored unfinished, but a node
deploys it as it will run: one with no workload, source, endpoint, command or
output has nothing to run, and an input with no description gives the installer
nothing to read (§5.4). A component that misses one is
ERR_INVALID_DEPENDENCY at the node's componentRef, semantic, in every
profile, as it was while those obligations were structural rules. The
component's own metadata.description
(COMP-DESC-003) is the one
exception: a node has deployed a component without one since component v1.1.0
made it optional, and v1 keeps accepting it.
BP-ID-003 requires every component document in the item to be
referenced. That rule, like every item-scoped check, cannot complete for a
standalone document without an item root (§3.1).
Each input's supplier lives at components.<node>.bindings.<input>. The map
key MUST name an input of the consuming component. The key present in a binding
says where the value comes from:
| Key | Members | Supplier |
|---|---|---|
parameter | parameter | A named installation parameter (§5) |
node | node, output | An output of another node |
value | value | A non-secret logical JSON value |
BP-CONN-003: A binding names exactly one supplier:
parameter, node with output, or value. A binding naming no supplier is
ERR_MISSING_FIELD at the binding, and one carrying members of two suppliers is
ERR_INVALID_VALUE there. node and output go together: node without
output and output without node are both ERR_MISSING_FIELD at the binding.
These are structural rules. Duplicate input keys fail during parsing, and there
is no override priority between suppliers.
bindings:
region:
parameter: region
databaseURL:
node: database
output: connectionString
mode:
value: production
Values from outside the documents enter only through parameters. A binding wires what the documents contain: a parameter, another node's output, or a literal. It never reads an organization variable or a connection itself; it names a parameter that does (§5). A logical schema constrains a value, and the key of its binding selects its supply.
BP-CONN-002: The value-dependency graph MUST be
acyclic. An output whose from is input adds a dependency on that input, and
a node binding adds a dependency on the producer output it names. Endpoint
outputs depend on allocated addresses, not running processes, so discovery
cycles are permitted. A value cycle fails with ERR_VALUE_CYCLE. Traversal MUST
terminate.
A connections member, a toNode or toInput binding, and coverage by
matching names are rejected. Adding an unrelated node MUST NOT change existing recipients.
Compute. compute names the compute a node runs on: a compute profile,
and the placement pins that narrow where that profile is placed
(§4.4).
BP-NODE-001: A node whose component runs, a
SERVICE, WORKER or JOB (component §5),
MUST carry compute. A node without it is ERR_CONFLICTING_NODE_COMPUTE at the
node.
BP-NODE-002: A node whose component is EXTERNAL
(component §5.6) MUST NOT carry compute.
Nothing runs, so there is nothing to size or place. compute on one is
ERR_CONFLICTING_NODE_COMPUTE at compute.
Both rules are semantic: the category belongs to the component, so they are
decided against the contract the node deploys. While that contract is
unavailable, they are deferred and the result is INCOMPLETE, as
BP-REF-002 states for every rule that reads the component.
BP-NODE-003: compute.profile is REQUIRED within
compute, and names exactly one compute profile. Placement narrows only the
profile beside it, so compute carrying placement and no profile is
ERR_MISSING_FIELD at compute, a structural rule.
A compute profile slug is family.tier.size, for example
general.standard.small. Families: general, compute, memory, storage, gpu,
accelerator. Tiers: economy, standard, performance, premium. Sizes: nano, small,
medium, large, xlarge. Catalog membership and regional availability are
capability checks. Initial resolution selects a version of the profile and pins
it; redeploy uses that version. Updating to the current catalog version is
explicit.
Storage. BP-NODE-004: volumes.<name>.sizeGiB
allocates each volume the component declares
(component §5.5) explicitly, at or above
its minSizeGiB. A declared volume with no allocation, an allocation naming no
declared volume, and an allocation below the minimum fail with
ERR_INVALID_VOLUME_ALLOCATION, a semantic rule. No storage default is selected
implicitly.
An allocation MAY also pin storageClass, a storage class term, and minIOPS,
a positive integer floor on provisioned IOPS. They describe the volume, not the
host, so they sit beside its size and never under placement, where they are
ERR_UNKNOWN_FIELD. Like a placement pin, each narrows what may serve the
allocation and never changes it: an unsupported term, or no storage able to meet
the floor, rejects capability admission, and neither is silently ignored. Their
terms follow the same grammar and the same catalog rules as placement terms
(§4.4).
Exposure. BP-NODE-005: exposure.<endpoint>
is PUBLIC or PRIVATE, or the object form §4.5 defines, whose
visibility is one of the two. An endpoint left out is PRIVATE. The rules
below read the endpoint's visibility, whichever form states it. The component
constrains the choice:
ERR_UNKNOWN_ENDPOINT.WORKER endpoint MUST NOT be PUBLIC: a worker is not request-driven, and
its endpoints serve the platform and sibling nodes
(component COMP-TYPE-003).
PUBLIC on one fails with ERR_ENDPOINT_NOT_EXPOSABLE.PUBLIC HTTP-family endpoint of a SERVICE requires the component to
declare a readiness probe
(component §5.4), otherwise
ERR_READINESS_REQUIRED.publicHostname, publicPort, publicAddress or publicURL,
component §5.2) requires that endpoint
to be PUBLIC, otherwise ERR_ENDPOINT_NOT_PUBLIC.Each diagnostic anchors at exposure/<endpoint>, except that
ERR_ENDPOINT_NOT_PUBLIC for an endpoint left out of exposure anchors at the
node's componentRef. These are semantic rules.
Exposure decides who reaches an endpoint, and the component decides how the
platform connects to it. Traffic for a PUBLIC endpoint reaches the workload
over the endpoint's protocol and trust policy, the same ones its probes use
(component COMP-EP-007), whatever
scheme the public used to reach the platform.
Placement constraints narrow the hosts eligible for the selected compute profile. They do not substitute a different profile or grant extra resources. Omitted placement, empty placement and empty array pins impose no additional constraint. Arrays cannot repeat terms.
| Pin | Requirement on an eligible host |
|---|---|
| cpuArchitectures | One of the listed architectures |
| cpuDedication | dedicated requires dedicated vCPU; shared imposes no dedication constraint |
| minAcceleratorMemoryGiB | At least this accelerator memory |
| acceleratorRuntimes | All listed runtimes are supported |
| acceleratorInterconnect | The named interconnect is available |
| acceleratorSKUClass | The named SKU class is available |
| networkClass | The named network class is available |
Storage is pinned on the volume it describes, not here (§4.3).
Numeric pins are positive integers. Terms use the schema's lowercase-token grammar. Catalog terms and their provider mappings are versioned context, not author-defined escape hatches. Missing context is INCOMPLETE. An unsupported term or no eligible host rejects capability admission; neither is silently ignored. The selected profile version and allocations are pinned by the resolution record. A future meaning change to a pin is a specification change, even if its spelling still passes structural validation.
An exposure answers three questions. Can the endpoint be reached from outside the installation? Must the platform sign a viewer in before forwarding a request? And does the workload learn who the viewer is, and how? The bare form answers only the first; the object form answers all three, and says which facts about the viewer the workload receives and which requests pass without a viewer:
exposure:
web:
visibility: PUBLIC
access: AUTHENTICATED
viewerIdentity: HEADER
| Member | Values | Absent |
|---|---|---|
visibility | PUBLIC, PRIVATE | REQUIRED |
access | OPEN: every request is forwarded. AUTHENTICATED: only viewers the installation's access policy authorizes | OPEN |
viewerIdentity | NONE: nothing is forwarded about the viewer. HEADER: the viewer's identity, in a request header. ASSERTION: a signed token naming the viewer, in a request header. OIDC: the workload signs viewers in itself, as an OpenID Connect client of the platform | NONE |
viewerClaims | A unique, non-empty list of EMAIL, the viewer's verified email address, and NAME, their display name | Only the identity |
accessExemptions | A unique, non-empty list of PATHS and BEARER: the requests the component declares it takes without a viewer (BP-NODE-008) | None |
Bare PUBLIC is { visibility: PUBLIC }, and so
{ visibility: PUBLIC, access: OPEN, viewerIdentity: NONE }; bare PRIVATE is
{ visibility: PRIVATE }. Public does not mean anonymous, and private does not
mean authenticated: AUTHENTICATED protects a workload that never reads an
identity, and a PRIVATE endpoint serves the platform and sibling nodes, which
the platform's sign-in is not in front of.
BP-NODE-006: The object form is closed, and
requires visibility: an absent one is ERR_MISSING_FIELD at the endpoint's
exposure, and an unknown member is ERR_UNKNOWN_FIELD there. Every other member
applies only to PUBLIC, so any of them under PRIVATE is ERR_INVALID_VALUE
at that member. Every viewerIdentity but NONE requires AUTHENTICATED,
because an identity the platform has not verified is one the caller supplied,
and because the platform keeps refusing a revoked viewer only while it stands in
front of the workload (BP-ACCESS-005). So a mode without
access: AUTHENTICATED is ERR_INVALID_VALUE at viewerIdentity, and so is
accessExemptions at accessExemptions. A claim needs a mode to travel in, so
viewerClaims without a viewerIdentity other than NONE is
ERR_INVALID_VALUE at viewerClaims. An empty or repeating list is
ERR_INVALID_VALUE at the list. These are structural rules.
BP-NODE-007: The component constrains access as
it constrains exposure:
HTTP, HTTPS and WS, so
access: AUTHENTICATED on an endpoint of any other protocol fails with
ERR_ENDPOINT_NOT_HTTP at exposure/<endpoint>/access.viewerIdentity to be the mode the property belongs to, and, for
viewerEmailHeader and viewerNameHeader, its viewerClaims to hold EMAIL
or NAME; otherwise ERR_VIEWER_IDENTITY_NOT_FORWARDED. It anchors as
ERR_ENDPOINT_NOT_PUBLIC does: at exposure/<endpoint>, or at the node's
componentRef for an endpoint left out of exposure.BP-NODE-008: The component declares what a node
may select beyond sign-in:
viewerIdentity: OIDC requires the endpoint to declare oidc
(component §5.2), otherwise
ERR_ENDPOINT_NOT_OIDC_CLIENT at exposure/<endpoint>/viewerIdentity.PATHS in accessExemptions requires the endpoint's accessExemptions to
declare paths, and BEARER requires it to declare bearer: true
(component §5.2), otherwise
ERR_EXEMPTION_NOT_DECLARED at that list entry. Without the component's
statement that every route checks its tokens, a node could open a workload
that checks nothing.These and BP-NODE-007 are semantic rules. A workload that
needs the values binds the outputs that read them back into its own inputs. That is a discovery
dependency on allocated facts, not a value cycle
(BP-CONN-002):
components:
web:
componentRef: ./components/web.yaml
compute: { profile: general.standard.small }
exposure:
web: { visibility: PUBLIC, access: AUTHENTICATED, viewerIdentity: HEADER }
bindings:
identityHeader: { node: web, output: identityHeader }
trustedProxies: { node: web, output: trustedProxies }
BP-ACCESS-001: The public routing facts of an
endpoint allocation
(component §5.2) carry a viewer identity
property only when that endpoint's exposure selects the property's mode, and,
for viewerEmailHeader and viewerNameHeader, its claim: viewerIdentityHeader
and trustedProxyCIDRs under HEADER, the header of each released claim under
HEADER, and the four viewerAssertion* facts under ASSERTION. Every header
name is a lowercase field name of 1 to
128 characters, RFC 9110 tchars only, and never authorization,
connection, content-length, cookie, forwarded, host, keep-alive,
origin, proxy-authorization, proxy-connection, te, trailer,
transfer-encoding, upgrade, x-real-ip or a name beginning
x-forwarded-, and no two facts of one endpoint name the same header. The
CIDRs are a non-empty list without repeats, each an IPv4
prefix in dotted decimal without leading zeros or an IPv6 prefix in RFC 5952
text, with a prefix length of at least 1 and no host bits set: a list that
trusts every address is not a trust decision. viewerAssertionKeysURL is an
absolute https URL without credentials, query or fragment, and
viewerAssertionIssuer and viewerAssertionAudience are non-empty strings of
at most 512 characters. An endpoint whose mode is OIDC has, beside its routing
facts and never among them, a client registration: an opaque identity and
version, the issuerURL, an absolute https URL as above, the clientID,
and the secret, the last two non-empty strings of at most 512 characters. It
is private installation state, like a generated credential. A fact on another
endpoint, or one breaking this grammar, is ERR_INVALID_RESOLUTION_CONTEXT in
the allocation stage. A fact the context does not supply leaves the value that reads it
unresolved, and resolution INCOMPLETE; no implementation supplies a fallback.
The rest of this section is what the platform does for an endpoint whose access
is AUTHENTICATED. No document phase observes a forwarded request, so these
requirements are verified against implementations, not documents.
BP-ACCESS-002: The platform MUST authenticate
the viewer, and authorize them against the installation's access policy, before
it forwards any request an exemption does not cover
(BP-ACCESS-009, BP-ACCESS-010), on HTTP
requests and WebSocket upgrades alike. The
policy belongs to the installation and is resolved in its owning organization
and project; no document names a principal, role or policy in v1. When the
authentication or authorization state it needs is missing, unknown or
unavailable, the platform MUST refuse the request and MUST NOT forward it. An
implementation that does not understand AUTHENTICATED MUST reject the
exposure rather than treat it as OPEN. Health probes reach the workload
directly (component §5.4) and are not
subject to viewer authentication.
BP-ACCESS-003: Under viewerIdentity: HEADER
or ASSERTION, the platform MUST remove every inbound copy of each header the
endpoint's facts name, and of Forwarded, X-Forwarded-* and X-Real-IP,
before it sets its own, on HTTP requests and WebSocket upgrades alike. The
identity it sets is a stable, opaque identifier of the authenticated viewer,
never an email address and never a credential. Under HEADER it sets the header
of each released claim too, and omits the email header for a viewer without a
verified email address. A claim header's value is the claim's UTF-8 text with
every octet outside visible ASCII, and every %, percent-encoded (RFC 3986
§2.1), so that no value can end a header or be read two ways. It MUST preserve
the validated Host, the Origin, and the request's own Authorization header
and application cookies, and MUST NOT forward its own session credential to the
workload. Under viewerIdentity: NONE and OIDC it forwards no identity
header.
This paragraph is informative. The header names are the platform's choice,
and no document depends on them. A platform does best to name them without the
X- prefix (RFC 6648), and under one prefix it owns, such as
<platform>-viewer-id, so that it removes inbound copies by that prefix rather
than by a list of names that can miss one.
BP-ACCESS-004: trustedProxyCIDRs MUST cover
every address the workload observes as the peer of traffic the platform
forwards to it, and no address from which another workload, another tenant or
the public can open a connection to it. The values are allocated before the
workload starts (BP-RESOLVE-001), and a change to them is
a change of allocation, so a new snapshot and a new record.
BP-ACCESS-005: The platform MUST bound how long
a connection it admitted, such as an upgraded WebSocket, may outlive the
authorization that admitted it, after a sign-out, an expiry or a withdrawn
permission, and MUST document that bound.
BP-ACCESS-006: One person has one subject in one
installation. The identity a HEADER forwards, the sub of an assertion and of
an OpenID Connect ID token, and deployment.installer.identity
(BP-PARAM-011) MUST be the same string for the same person
in the same installation, and MAY differ between installations. A released
EMAIL is an address the platform has verified for the viewer.
BP-ACCESS-007: Under ASSERTION, the platform
MUST set, on every request and WebSocket upgrade it forwards, a JWS compact
serialization of a JWT signed with RS256, whose protected header names its key
in kid. Its claims are iss and aud, equal to the endpoint's
viewerAssertionIssuer and viewerAssertionAudience; sub; iat; exp, at
most 600 seconds after iat; and each released claim, as email with
email_verified and as name. The audience MUST be unique to one endpoint of
one installation. The key set at viewerAssertionKeysURL MUST hold every key
whose tokens have not yet expired, so a retired key stays published until the
last token it signed expires.
BP-ACCESS-008: Under OIDC, the platform MUST
register one confidential client per endpoint of the installation, whose
redirect URIs are exactly the endpoint's publicURL followed by each of its
redirectPaths, compared exactly (RFC 9700 §2.1). A reallocation that changes
publicURL re-registers them. The issuer MUST support the authorization code
flow with PKCE (S256), MUST issue a code only to a viewer the installation's
access policy authorizes (BP-ACCESS-002), and releases in
the ID token and user information only the subject and the released claims; a
scope asking for more is not refused, and gets no more. Rotating the client
secret is a change of allocation. Deleting the installation MUST revoke the
client.
BP-ACCESS-009: Under
PATHS, the platform MUST forward a request whose path lies under one of the
component's exempt paths as it would under access: OPEN: without
authenticating a viewer, with no identity, claim or assertion header, and with
inbound copies of them removed as BP-ACCESS-003 says. A path
lies under an exempt path when it equals it, or begins with it followed by /,
so /webhook covers /webhook/abc and never /webhooks. The query takes no
part in matching. The platform matches the path after decoding percent-encoded
unreserved characters and removing dot segments (RFC 3986 §6.2.2), forwards the
path it matched, and MUST NOT exempt a request whose path holds ;, an encoded
/ or \, a \, a NUL or an empty segment. The platform serves its own
sign-in under /.musher on an endpoint whose access is AUTHENTICATED, and no
exemption reaches it.
BP-ACCESS-010: Under BEARER, the platform MUST
forward a request that carries no valid viewer session and an Authorization
header of the Bearer scheme, with no identity, claim or assertion header, for
the workload to judge. A request that carries a valid session is authenticated,
authorized and identified as any other is, whatever else it carries.
spec.parameters lists everything an installation takes from outside the
documents: values a person submits, generated credentials and their hashes,
organization variables, facts about the deployment, and connections. Node bindings only wire what the documents contain
(§4.2), so a value from outside enters as a parameter, and a node
reaches it by naming the parameter. Parameters are named installation values,
independent of form presentation.
A parameter is supplied in exactly one way, and the keys present say which:
| Supply | Declared by | Value |
|---|---|---|
| Submitted | No generator, hash or from; optional default | The value submitted at installation, else default |
| Generated | generator | A persisted random credential (§5.2) |
| Hash | hash | A persisted password hash of a generated parameter (§5.2) |
| Variable | from: "${{ variables.<path> }}" | One organization variable (§5.2) |
| Deployment fact | from: "${{ deployment.<path> }}" | One fact about the installation, captured when it is created (§5.2) |
| Connection | from: "${{ connections.<path> }}" | One atomic connection (§5.3) |
parameters:
siteTitle: { ui: { label: Site title }, default: My site }
dbPassword: { generator: { byteLength: 32, encoding: BASE64URL } }
dbPasswordHash: { hash: { parameter: dbPassword, algorithm: BCRYPT } }
region: { from: "${{ variables.cloud.region }}" }
adminEmail: { from: "${{ deployment.installer.email }}" }
llm: { from: "${{ connections.llm.default }}", ui: { label: Language model } }
The node in §4's example reaches siteTitle through its
bindings, and §5.3 shows a connection reaching a node
the same way. A node never names a variable or a connection directly.
BP-PARAM-010: A parameter carries at most one of
default, generator, hash and from. A default beside generator or from is
ERR_INVALID_VALUE at default, and a from beside generator is
ERR_INVALID_VALUE at from. A hash is never offered in a form, so a
default, generator, from or ui beside hash is ERR_INVALID_VALUE at
that member. hash requires parameter and algorithm. These are structural
rules.
A parameter MAY also carry ui (§5.4). Parameters declare no
schema of their own; the inputs bound to them own the value contract
(§5.1).
BP-PARAM-001: Every parameter MUST be named by at
least one parameter binding; otherwise ERR_UNBOUND_PARAMETER. The one
exception is a generated parameter that carries ui and that a hash names: it
reaches the installer through disclosure (BP-PARAM-012)
without a binding of its own.
BP-PARAM-002: Shared parameters require equal
logical schemas, ignoring mapping order. Otherwise ERR_CONFLICTING_INPUT_SCHEMA.
Descriptions, target locations and input defaults do not define shared identity.
Sensitivity is the union of the receiving contracts and the supplied value.
BP-PARAM-003: Without a binding, an input uses its
component default, remains absent if optional, or fails with
ERR_UNSATISFIED_REQUIRED_INPUT. A bound but unavailable supplier never selects
the input default.
BP-PARAM-006: A node binding names an existing
node (ERR_UNKNOWN_NODE) and an output its component declares
(ERR_UNKNOWN_OUTPUT).
BP-PARAM-007: Every binding names an existing
input (ERR_UNKNOWN_INPUT), and every parameter binding names a
declared parameter (ERR_UNKNOWN_PARAMETER).
BP-PARAM-004: A generator produces a sensitive
string, which is never submitted: a submitted value for a generated parameter
fails with ERR_PARAMETER_NOT_SUBMITTABLE (BP-PARAM-009).
byteLength is an integer from 16 to 64, default 32. Generate that many
cryptographically secure random bytes. encoding defaults to HEX (lowercase);
BASE64 uses the standard padded alphabet; BASE64URL uses the URL-safe alphabet
without padding. Custom alphabets are unsupported. The generated string must
satisfy every receiving schema; failing a constraint must not regenerate it.
BP-PARAM-012: A hash names, in parameter, a
generated parameter of the same blueprint: an undeclared one is
ERR_UNKNOWN_PARAMETER, and one supplied any other way is
ERR_INVALID_HASH_SOURCE, both at hash/parameter. bcrypt reads at most 72
bytes and ignores the rest, so a BCRYPT hash requires its source to encode to
72 characters or fewer: a byteLength of at most 36 in HEX, and of at most 54
in BASE64 or BASE64URL; otherwise ERR_INVALID_HASH_SOURCE at
hash/algorithm. Like a generated value, a hash is a string that must satisfy
every receiving schema, so a receiver whose schema is not a string is
ERR_VALUE_CONSTRAINT at hash. These are semantic rules. algorithm fixes
the whole format:
| Algorithm | Value |
|---|---|
BCRYPT | $2b$12$, then 53 characters of the bcrypt alphabet: salt and hash |
ARGON2ID | The PHC string $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>, with a 16-byte salt and a 32-byte hash in unpadded standard base64 |
A hash is computed once, with a fresh random salt, when its source is
generated, and again only when its source rotates, in the same atomic step. It
is stored as a credential of its own, with its own identity and its source's
rotation generation, and it is sensitive. It is never submitted. A stored hash
that does not have its algorithm's format is ERR_INVALID_RESOLUTION_CONTEXT.
A generated parameter that carries ui MAY be disclosed to the people who
manage the installation, through a private channel, and never in a published
artifact, a public response, a preview or a diagnostic. That is how an item that
binds only a hash stays usable: whoever installed it can read the password the
hash was made from.
BP-PARAM-005: Parameter defaults are logical
literals, without interpolation. A submitted value replaces the default. Unknown
submitted parameter keys fail with ERR_UNKNOWN_PARAMETER before defaults are
applied, so a misspelling cannot be silently ignored. A submitted parameter with
no submitted value and no default is ERR_MISSING_PARAMETER_VALUE at
installation.
BP-PARAM-008: Every statically known supplied
value must satisfy the inputs that receive it. Dynamic values are checked after
resolution, before execution. Producer and consumer logical types must match,
except that an integer may supply a number. This is conservative type
compatibility, not proof of schema containment.
BP-REF-001: from is exactly one whole reference
under core's grammar, in the
variables, deployment or connections namespace. The namespace says what
kind of entry the reference names: variables one value, deployment one fact
about the installation, connections one atomic connection.
It cannot concatenate, interpolate other text, hold two references, select a
component or fall back. A from that breaks this fails with:
ERR_MALFORMED_REFERENCE when a ${{ does not begin a well-formed reference
(CORE-REF-001);ERR_INVALID_PARAMETER_SOURCE when its references are well formed but it is
not exactly one whole reference;ERR_UNKNOWN_REFERENCE_NAMESPACE when the namespace is one core does not
reserve, such as config;ERR_REFERENCE_NOT_IN_SCOPE when the namespace is reserved but is none of
variables, deployment and connections
(core v1 §5.2).All anchor at the parameter's from and are semantic rules. A blueprint never
references a component's endpoints: component defines them, and a node consumes
them only through the outputs a component names explicitly.
BP-PARAM-009: A parameter whose from is in the
variables namespace takes one organization variable. Its dotted path is an
exact key, not object traversal or a registry type. A variable resolves against
the configuration visible to the environment the installation deploys into:
organization values, which an environment MAY override. Acquisition supplies
its identity, version, logical value and sensitivity. An unavailable variable
is INCOMPLETE; a variable the installation may not read is
ERR_VARIABLE_NOT_AUTHORIZED; a variable without identity or version is
ERR_INVALID_RESOLUTION_CONTEXT. Each is reported once per parameter, however
many bindings name it, at /spec/parameters/<name>/from in the parameters
stage of the resolution phase, and an unavailable variable is deferred there
under this rule. No network lookup occurs during semantic validation.
A parameter carrying from or hash, like a generated one, is never
submitted. A submitted value for any of them is ERR_PARAMETER_NOT_SUBMITTABLE at
/spec/parameters/<name>, in the resolution phase. A connection is replaced
whole through acquisition instead (§5.3).
BP-PARAM-011: A parameter whose from is in the
deployment namespace takes one fact about the person who created the
installation. The namespace defines exactly three paths, and any other is
ERR_UNKNOWN_DEPLOYMENT_FACT at from, a semantic rule:
| Path | Fact |
|---|---|
deployment.installer.identity | Their stable, opaque identifier, the same one the installation's endpoints forward for them (BP-ACCESS-006) |
deployment.installer.email | Their verified email address |
deployment.installer.name | Their display name |
A fact is captured once, when the installation is created, and kept in its
private snapshot, so a redeploy, an update or an action by another person never
changes it. It is sensitive: personal data, not a secret, kept out of public
inspection, previews and diagnostics all the same. A fact acquisition has not
supplied leaves resolution INCOMPLETE. A fact the platform knows it lacks, such
as the email address of an installation a service principal created, is
ERR_DEPLOYMENT_FACT_UNAVAILABLE, because waiting would never supply it. Both
anchor at /spec/parameters/<name>/from in the parameters stage of the
resolution phase, and a malformed fact is ERR_INVALID_RESOLUTION_CONTEXT
there.
BP-RESOLVE-001: Resolution completes these steps
before any workload starts: validate pinned contracts; select compute, storage
and exposure; allocate endpoint addresses; obtain submitted values, variables,
connections and persisted generated values; evaluate value dependencies;
validate every input and output; encode workload environment values. Incomplete
or invalid resolution forbids execution. A missing required installation
parameter is invalid; missing external context is incomplete. An output
forwarding an absent optional input fails with ERR_UNSATISFIED_REQUIRED_INPUT
at the output.
Generated credentials are created once per installation identity, parameter identity and non-negative rotation generation. Atomic durable get-or-create persists them before use. Retries and updates reuse them. Rotation explicitly increments the generation. Renaming an identity requires an explicit migration or a new credential; reconciliation cannot silently rotate it.
Resolution produces a resolution record. It is generated, not an authored
override layer, and its version is 1. Its executable shape is
#/$defs/BlueprintResolutionRecord in the self-contained blueprint bundle. It is
a generated JSON artifact, not another Musher document kind, and unknown record
fields are rejected. It is read by software, not written by people, so it keeps
its source tag where an authored document names a source by the key present.
What it records. The record contains:
variables;Each component records source IMAGE, GIT or EXTERNAL. External nodes forbid image, commit and compute fields and have empty volume and exposure maps. All other nodes require an image digest and compute identity/version; GIT additionally requires the full commit object ID. No secret plaintext or secret-content hashes are included.
The installation snapshot. The required installationSnapshot pins the
private state behind the record by an opaque identity and an immutable version.
That private snapshot uses formatVersion 1. It persists submitted parameter
values, selected variable values and their identity/version pairs, captured
deployment facts, credential references and rotation generations for generated
credentials and their hashes, endpoint allocations with any OpenID Connect
client registration, and any connection selections, including source-policy
revisions.
Its physical storage format is implementation-defined. Its sensitive values are
private and never hashed into the public record, and no public secret-derived
hash stands in for its identity. Reusing an identity/version for changed state is
forbidden: any change to a selected value, dependency, rotation or allocation
produces a new snapshot version and a new record. Persist the snapshot before
materialization.
Accepting a record. Acceptance parses and validates the record's blueprint
and referenced contracts, resolves its pinned component artifacts, and
reconciles the exact node set, identities, revisions, artifact digests, workload
sources and their kinds, compute identities, volumes, exposure and allocation
choices, acquired dependencies and specification release graph. Pinned image
digests and authored Git commits must agree. The variables map and the
credentials map must match the variable and generated parameters the blueprint
binds, the hash parameters it binds and the generated parameters they hash, and
the private snapshot. A hash carries an identity of its own and its source's
rotation. Required endpoint allocation identities must be
present. Replaying the snapshot must successfully resolve the blueprint without
unpersisted selections. Exact specification dependency manifests must name all
declared dependencies and agree on shared editions; missing dependencies or
extra unrelated families fail. Schema validity alone is insufficient. The same
accepted record MUST identify the same effective values, not identical behavior
from external services.
A Git commit alone does not establish build reproducibility; the resulting image digest is required. Redeploy uses the existing record and fails when a pinned dependency is unavailable. Updating produces a new record explicitly. Operational eligibility can change independently, but cannot rewrite the record.
A connection parameter is a parameter whose from is
${{ connections.<path> }}. It names one atomic connection
(ADR 0030): an endpoint, a
credential and a model acquired together, not a scalar lookup. It fills a
connection input (component §6.4)
through an ordinary parameter binding. The component that declares the input
is an external node standing for the model API, and the nodes that call the
model wire their inputs to its outputs, as they would to a managed database's.
The dotted path after connections. is an exact key naming one connection the
organization has configured, for example llm.default; it is not object
traversal and not a selector.
parameters:
llm: { from: "${{ connections.llm.default }}", ui: { label: Language model } }
components:
llm:
componentRef: ./components/openai-chat.yaml
bindings:
llm: { parameter: llm }
assistant:
componentRef: ./components/assistant.yaml
compute: { profile: general.standard.small }
bindings:
llmBaseURL: { node: llm, output: baseURL }
llmAPIKey: { node: llm, output: apiKey }
llmModel: { node: llm, output: model }
BP-CONNECTION-001: A connection input MUST be
bound only by a parameter binding naming a connection parameter, and a
connection parameter MUST bind only connection inputs. Each of these fails with
ERR_INVALID_CONNECTION_BINDING:
value or node binding to a connection input, at the binding;parameter binding to a connection input naming a parameter that is not a
connection parameter, at the binding's parameter;parameter binding to a value input naming a connection parameter, at the
binding's parameter.A binding naming no parameter is ERR_UNKNOWN_PARAMETER
(BP-PARAM-007), and nothing else. A connection input is always
required, so one left unbound is ERR_UNSATISFIED_REQUIRED_INPUT
(BP-PARAM-003). A connection input declares no schema, so
BP-PARAM-002 does not compare it. The parameter's from must
satisfy BP-REF-001. There is no implicit sharing by input name
or protocol. A variables parameter holds one value, and separate variables
cannot assemble a connection.
BP-CONNECTION-002: Installation acquires one
immutable, authorized selection per installation identity and connection
parameter. All consumers of that parameter use protocol views of that same
selection. Each view MUST satisfy the requested protocol, capabilities, and
individual input schemas. Failure is ERR_CONNECTION_INCOMPATIBLE or
ERR_VALUE_CONSTRAINT; never select another provider as fallback. Values enter
the existing input/environment pipeline. No input, output or environment values
are released on incomplete or invalid resolution, including when only one of
several connection parameters fails.
Acquisition distinguishes NOT_ACQUIRED (INCOMPLETE), authoritative NOT_FOUND
(ERR_CONNECTION_NOT_FOUND), DENIED (ERR_CONNECTION_DENIED), INCOMPATIBLE
(ERR_CONNECTION_INCOMPATIBLE), and SELECTED. The synthetic context represents a
SELECTED result with persisted: true and a selection containing identity,
version, installation, parameter, source reference/identity/version, kind
MANAGED or USER, costOwner, credential identity/rotation/value/permittedBaseURLs,
and protocol-keyed views. The source reference MUST equal the connection
parameter's from, including for a complete USER replacement. It identifies the
acquisition request, not the selected provider. Managed credential identities
MUST NOT be reused across distinct connection parameters. Each view has baseURL,
model and capabilities. Endpoints are absolute HTTPS URLs without user
information, query or fragment. Every view URL must belong to the credential's
permittedBaseURLs. Context is trusted acquisition evidence, not an authored
mechanism for granting permission. Offline evaluation performs no network or
credential issuance. Malformed evidence is ERR_INVALID_RESOLUTION_CONTEXT.
BP-CONNECTION-003: Persist the complete selection
and scoped credential before materialization. Retry and redeploy reuse the selected
identity/version and credential. Changes to organization defaults affect only new
installations. An explicit update replaces the complete selection and produces a
new private snapshot and resolution record. Rotation is durable and idempotent;
clone creates a new installation and distinct scoped credential. Revocation denies
use immediately, regardless of an existing record. Failed attempts must not leave
multiple active credentials or silently change provider on retry.
Overrides use a complete USER selection with its own endpoint, credential and
model; missing members fail. Partial merging with a managed selection is
forbidden. An override arrives through acquisition, never as a submitted
parameter value (BP-PARAM-009). Managed credentials are
scoped to installation, connection parameter and permitted service, not
unrestricted organization provider keys. Credential plaintext and its hashes
MUST NOT appear in published artifacts, forms, public responses, previews or
diagnostics. Installation interfaces disclose the selected connection and cost
owner. Existing organization policy may authorize automated selection without
repeated prompts.
Gateway base paths, upstream providers, default models, quotas, spending limits, current authorization and durable persistence are platform responsibilities. Real SDK integration must verify client paths, authentication, streaming, tool calls and errors. Synthetic fixture credentials establish no production access.
BP-UI-001: ui is optional; when supplied it
requires label and rejects unknown properties.
BP-UI-002: prominence is PRIMARY (default) or
SECONDARY.
BP-UI-003: enumLabels names only string
representations of scalar enum members, or of string-array item enum members.
Arrays and objects in a whole-value enum have no enumLabels keys.
BP-UI-004: A form offers each parameter that declares
ui, except a generated parameter, a variable parameter and a deployment fact:
none is ever an editable field. A form MAY show a deployment fact, read-only, to
the person it describes. A hash declares no ui and is never offered. A
connection parameter with ui is offered as one control
choosing a whole selection, never as its endpoint, credential or model. A
connection parameter without ui is not offered: installation acquires its
default selection without a form field.
For any other parameter, conceal sensitive values first; then offer enum choices, string-array item choices, boolean controls, structured JSON editing, or scalar text entry, in that order. UI examples are never submitted. Optional parameters without UI may be supplied by an installation API.
Order fields by ascending ui.order, unspecified orders last, then parameter
name in UTF-8 order. For shared parameters, help text is the description of the first
receiver by node name then input name; this ordering does not affect validation.
Clients validate logical values; server admission MUST independently validate
them as well. Labels and descriptions do not override contracts.
Core's explicit profiles and statuses apply. Structural-only success does not
claim publication or deployment validity. Local and published dependencies share
offline semantic checks; acquisition is a separate authorized operation.
Publication completes contract and catalog obligations, BP-ID-005
and BP-GRAPH-001 among them. Deployment additionally completes
installation resolution, authorization and current capability checks.
Core and component diagnostics apply, with these additions:
| Code | Phase | Meaning |
|---|---|---|
ERR_COMPONENT_NOT_FOUND | semantic | Local component file is absent. |
ERR_REFERENCE_ESCAPE | semantic | Component reference escapes the item. |
ERR_INVALID_DEPENDENCY | semantic | Supplied contract is invalid, fails identity/digest verification, or misses a component publication obligation. |
ERR_CONFLICTING_NODE_COMPUTE | semantic | A node whose component runs carries no compute, or an external node carries some. |
ERR_UNKNOWN_NODE | semantic | Binding names no node. |
ERR_NODE_REQUIRED | capability | Publication requires a node this blueprint does not declare. |
ERR_UNKNOWN_OUTPUT | semantic | Binding names no producer output. |
ERR_UNKNOWN_INPUT | semantic | Binding names no receiver input. |
ERR_UNKNOWN_PARAMETER | semantic, resolution | Binding, hash or submitted value names no parameter. |
ERR_INCOMPATIBLE_TYPE | semantic | Producer type cannot supply consumer. |
ERR_UNREFERENCED_COMPONENT | semantic | Item contains an unused component. |
ERR_CONFLICTING_INPUT_SCHEMA | semantic | Shared parameter receivers disagree. |
ERR_UNBOUND_PARAMETER | semantic | No binding names the parameter. |
ERR_UNSATISFIED_REQUIRED_INPUT | semantic, resolution | Required input has no binding or default. |
ERR_ENDPOINT_NOT_PUBLIC | semantic | Output requires exposure not selected by the blueprint. |
ERR_VIEWER_IDENTITY_NOT_FORWARDED | semantic | Output reads a viewer identity property of an endpoint whose exposure does not forward that property's mode or claim. |
ERR_EXEMPTION_NOT_DECLARED | semantic | An exposure lets through requests without a viewer that the component does not declare it takes. |
ERR_UNKNOWN_ENUM_MEMBER | semantic | UI label names no enum member. |
ERR_INVALID_PARAMETER_SOURCE | semantic | A parameter's from is not one whole reference. |
ERR_UNKNOWN_DEPLOYMENT_FACT | semantic | A parameter's from names a deployment path the namespace does not define. |
ERR_INVALID_HASH_SOURCE | semantic | A hash names a parameter that is not generated, or one too long for its algorithm. |
ERR_INVALID_VOLUME_ALLOCATION | semantic | Volume allocation is absent, unknown or below minimum. |
ERR_READINESS_REQUIRED | semantic | Public HTTP-family service has no readiness probe. |
ERR_VALUE_CYCLE | semantic | Value dependencies contain a cycle. |
ERR_INVALID_CONNECTION_BINDING | semantic | A connection input is bound by something other than a connection parameter, or a connection parameter binds a value input. |
ERR_PARAMETER_NOT_SUBMITTABLE | resolution | A submitted value names a generated, hash, variable, deployment fact or connection parameter. |
ERR_MISSING_PARAMETER_VALUE | resolution | Required submitted value is absent. |
ERR_VARIABLE_NOT_AUTHORIZED | resolution | Installation cannot read the organization variable. |
ERR_DEPLOYMENT_FACT_UNAVAILABLE | resolution | The platform knows it has no value for a deployment fact the installation reads. |
ERR_INVALID_RESOLUTION_CONTEXT | resolution | Resolution context is malformed, or lacks a required identity or version. |
ERR_CONNECTION_NOT_FOUND | resolution | Authorized acquisition confirms no selected default exists. |
ERR_CONNECTION_DENIED | resolution | Acquisition denies permission to use the connection. |
ERR_CONNECTION_INCOMPATIBLE | resolution | Selection cannot satisfy required protocol or capabilities. |
An implementation declares profiles and runs their family and dependency fixtures. The resolution profile includes sensitivity, absence, encoding and credential lifecycle observations. A skipped case is never passed.
Cross-installation component instances, conditional node sets, runtime job
outputs, startup dependency graphs, recursive templates, arbitrary provider
maps, a document naming the principals, roles or policy an AUTHENTICATED
endpoint admits, AUTHENTICATED access to a GRPC, TCP or UDP
endpoint, role or group claims, a viewer identity mode under access: OPEN,
machine tokens the platform itself accepts, exemptions by request method,
hashes of submitted values, other hash algorithms or parameters, and
deployment facts beyond the installer's are unsupported. Their declarations are rejected.
Treat documents and dependency artifacts as untrusted. Verify containment, digests, parser bounds and contracts before resolution. Variable and connection references select organization data the installation is authorized to read; they never grant access by themselves. No validation phase fetches a document-chosen URL. Resolved secrets remain in private materialization channels and cannot appear in diagnostics or exported resolution records.
A PUBLIC endpoint whose access is OPEN is reachable by anyone with its
address, and an identity header on one would be whatever the caller wrote. That
is why a component that reads the viewer identity properties cannot be deployed
without the viewerIdentity mode those properties belong to
(BP-NODE-007), and why the
platform refuses, rather than forwards, a request it cannot authorize
(BP-ACCESS-002).
A released claim is personal data that the workload stores and may send on, and
the platform cannot take it back. That is why a blueprint names each one in
viewerClaims, why a deployment fact is bound through a parameter
(BP-PARAM-011) that a reviewer reads, and why neither is
ever released by default.
An exemption opens part of an authenticated endpoint to anyone who can reach
it. PATHS opens only the routes a component names, and the platform never
exempts a path it could read two ways (BP-ACCESS-009).
BEARER opens every route to any request that presents a bearer token, valid or
not, so the endpoint is then exactly as strong as the workload's own token
check. Prefer PATHS. A cross-origin preflight to a route that is not exempt
carries no credentials, and is refused.
A hash is not the secret, but it can be attacked offline, so it is sensitive and never published (§5.2). The source of a hash is disclosed only to the people who manage the installation.