Reference / blueprint / v1.7.2

Musher Blueprint Document — Specification 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 description fields, 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.


1. Scope

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

2. Document envelope

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 parameterThis 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

SpecificationLine
corev1
componentv1

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.

3. Identity

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:

IDRuleDiagnostic
BP-ID-003Every 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.

3.1 The item directory

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.

4. Component graph

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 }
MemberHoldsSection
componentRef, revisionThe component document the node deploys§4.1
bindingsThe supplier of each input§4.2
computeThe compute profile and its placement pins§4.3, §4.4
volumesThe storage allocated to each volume§4.3
exposureThe exposure of each endpoint, and who may reach it§4.3, §4.5

4.1 Component reference

componentRef takes one of two forms:

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).

4.2 Explicit bindings

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:

KeyMembersSupplier
parameterparameterA named installation parameter (§5)
nodenode, outputAn output of another node
valuevalueA 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.

4.3 Compute, storage and exposure

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:

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.

4.4 Placement constraints

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.

PinRequirement on an eligible host
cpuArchitecturesOne of the listed architectures
cpuDedicationdedicated requires dedicated vCPU; shared imposes no dedication constraint
minAcceleratorMemoryGiBAt least this accelerator memory
acceleratorRuntimesAll listed runtimes are supported
acceleratorInterconnectThe named interconnect is available
acceleratorSKUClassThe named SKU class is available
networkClassThe 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.

4.5 Access and viewer identity

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
MemberValuesAbsent
visibilityPUBLIC, PRIVATEREQUIRED
accessOPEN: every request is forwarded. AUTHENTICATED: only viewers the installation's access policy authorizesOPEN
viewerIdentityNONE: 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 platformNONE
viewerClaimsA unique, non-empty list of EMAIL, the viewer's verified email address, and NAME, their display nameOnly the identity
accessExemptionsA 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:

BP-NODE-008: The component declares what a node may select beyond sign-in:

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.

5. Installation parameters

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:

SupplyDeclared byValue
SubmittedNo generator, hash or from; optional defaultThe value submitted at installation, else default
GeneratedgeneratorA persisted random credential (§5.2)
HashhashA persisted password hash of a generated parameter (§5.2)
Variablefrom: "${{ variables.<path> }}"One organization variable (§5.2)
Deployment factfrom: "${{ deployment.<path> }}"One fact about the installation, captured when it is created (§5.2)
Connectionfrom: "${{ 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).

5.1 Recipients

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).

5.2 Supply and resolution

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:

AlgorithmValue
BCRYPT$2b$12$, then 53 characters of the bcrypt alphabet: salt and hash
ARGON2IDThe 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:

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:

PathFact
deployment.installer.identityTheir stable, opaque identifier, the same one the installation's endpoints forward for them (BP-ACCESS-006)
deployment.installer.emailTheir verified email address
deployment.installer.nameTheir 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.

Generated resolution record

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:

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.

5.3 Atomic connections

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:

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.

5.4 Install-form presentation

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.

6. Validation layers

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.

7. Diagnostics

Core and component diagnostics apply, with these additions:

CodePhaseMeaning
ERR_COMPONENT_NOT_FOUNDsemanticLocal component file is absent.
ERR_REFERENCE_ESCAPEsemanticComponent reference escapes the item.
ERR_INVALID_DEPENDENCYsemanticSupplied contract is invalid, fails identity/digest verification, or misses a component publication obligation.
ERR_CONFLICTING_NODE_COMPUTEsemanticA node whose component runs carries no compute, or an external node carries some.
ERR_UNKNOWN_NODEsemanticBinding names no node.
ERR_NODE_REQUIREDcapabilityPublication requires a node this blueprint does not declare.
ERR_UNKNOWN_OUTPUTsemanticBinding names no producer output.
ERR_UNKNOWN_INPUTsemanticBinding names no receiver input.
ERR_UNKNOWN_PARAMETERsemantic, resolutionBinding, hash or submitted value names no parameter.
ERR_INCOMPATIBLE_TYPEsemanticProducer type cannot supply consumer.
ERR_UNREFERENCED_COMPONENTsemanticItem contains an unused component.
ERR_CONFLICTING_INPUT_SCHEMAsemanticShared parameter receivers disagree.
ERR_UNBOUND_PARAMETERsemanticNo binding names the parameter.
ERR_UNSATISFIED_REQUIRED_INPUTsemantic, resolutionRequired input has no binding or default.
ERR_ENDPOINT_NOT_PUBLICsemanticOutput requires exposure not selected by the blueprint.
ERR_VIEWER_IDENTITY_NOT_FORWARDEDsemanticOutput reads a viewer identity property of an endpoint whose exposure does not forward that property's mode or claim.
ERR_EXEMPTION_NOT_DECLAREDsemanticAn exposure lets through requests without a viewer that the component does not declare it takes.
ERR_UNKNOWN_ENUM_MEMBERsemanticUI label names no enum member.
ERR_INVALID_PARAMETER_SOURCEsemanticA parameter's from is not one whole reference.
ERR_UNKNOWN_DEPLOYMENT_FACTsemanticA parameter's from names a deployment path the namespace does not define.
ERR_INVALID_HASH_SOURCEsemanticA hash names a parameter that is not generated, or one too long for its algorithm.
ERR_INVALID_VOLUME_ALLOCATIONsemanticVolume allocation is absent, unknown or below minimum.
ERR_READINESS_REQUIREDsemanticPublic HTTP-family service has no readiness probe.
ERR_VALUE_CYCLEsemanticValue dependencies contain a cycle.
ERR_INVALID_CONNECTION_BINDINGsemanticA connection input is bound by something other than a connection parameter, or a connection parameter binds a value input.
ERR_PARAMETER_NOT_SUBMITTABLEresolutionA submitted value names a generated, hash, variable, deployment fact or connection parameter.
ERR_MISSING_PARAMETER_VALUEresolutionRequired submitted value is absent.
ERR_VARIABLE_NOT_AUTHORIZEDresolutionInstallation cannot read the organization variable.
ERR_DEPLOYMENT_FACT_UNAVAILABLEresolutionThe platform knows it has no value for a deployment fact the installation reads.
ERR_INVALID_RESOLUTION_CONTEXTresolutionResolution context is malformed, or lacks a required identity or version.
ERR_CONNECTION_NOT_FOUNDresolutionAuthorized acquisition confirms no selected default exists.
ERR_CONNECTION_DENIEDresolutionAcquisition denies permission to use the connection.
ERR_CONNECTION_INCOMPATIBLEresolutionSelection cannot satisfy required protocol or capabilities.

8. Conformance

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.

9. Unsupported capabilities

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.

10. Security considerations

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.