feat(component)!: a node this platform does not run — ADR 0019, on ADR 0007 and ADR 0009 - #80
Merged
Merged
Conversation
…ier grammars
ADR 0007 has been accepted and unimplemented since 2026-08-19. It is the first
of the two breaking ADRs queued behind the 1.0.0 release pull requests, and its
window closes the moment any family is tagged. This lands §1 through §4.
§1 renames the fields whose names asserted something false about themselves. A
boolean is an adjective, so `isRequired`, `isSensitive` and `isReadOnly` become
`required`, `sensitive` and `readOnly`. `kind` names a document family and
nothing else, so `workload.kind` becomes `workload.type` — the document
envelope's `kind` is untouched, and the separation is what the rename exists
for. A reference to another object ends `Ref`, so the blueprint node's
`component` becomes `componentRef`. A collection's name is plural, so
`cpuArchitecture` and `acceleratorRuntime` become `cpuArchitectures` and
`acceleratorRuntimes`. A name says what a value is rather than how advanced it
is, so `advanced` becomes `placement`.
§2 spells the units correctly: `sizeGib` becomes `sizeGiB`,
`acceleratorMinVramGb` becomes `minAcceleratorMemoryGiB`, `storageMinIops`
becomes `minStorageIOPS`, and `acceleratorSkuClass` becomes
`acceleratorSKUClass`. A field name that picks the wrong unit is a
specification asserting something false about the number beside it.
§3 gives `version` back to the specification. `metadata.version` becomes
`metadata.revision` in all three families and the blueprint node's
`componentVersion` becomes `revision`; `specVersion: v1` keeps its name.
Component §4 spent five paragraphs telling a reader the field was not what it
was called, and those paragraphs now say `revision` in the one word a reader
already holds.
§4 gives every user-authored mapping key a grammar. Seven were unconstrained,
which meant the contract accepted an input named with a single space and a
volume named `../../etc` and had nothing to say about either: input, output and
parameter names and connection keys take `^[a-z][a-zA-Z0-9]{0,63}$`, volume
names take the node-name DNS label, build-argument names take the
environment-variable grammar they become, and a listing tag takes lowercase
kebab. Each is stated in prose where the identifier is defined, in the shape
§5.2 already uses for an endpoint name — phase and code, no requirement
identifier, because that is the precedent this repository set for a grammar.
The grammars rejected the corpus's own spelling, which is the finding worth
recording: four inputs and eight connections were named `DATABASE_URL` and its
siblings — the environment-variable key wearing the input's clothes, when the
env key is what `target` carries. They are `databaseUrl` and its siblings now,
and `structural/053` pins the rejection so the confusion cannot come back.
Seven negative fixtures land with it, one per new grammar. Each anchors at the
containing object rather than the offending key, because that is what
`propertyNames` reports and what `structural/012` and `structural/029` already
declare for the two grammars that existed before.
This is one commit rather than three because the corpus interlocks: every
family's conformance trees embed the other families' documents, so an input
rename and the connection-key rename that has to match it cannot be separated
without a red commit in between. All three packages pick it up by path.
§5 — removing the `"null"` branches from fields where absence already means the
same thing — is not in this commit and is the remainder of ADR 0007.
BREAKING CHANGE: every rename above rejects a document that spells the old name,
and the seven new identifier grammars reject names that validated before.
ADR 0005 §1's pre-publication window applies: no `v<N>` directory and no
migration note, since `git tag -l` is empty and `published.json` records no
release. Maintainer approval is the obligation the window does not remove.
Refs ADR 0007.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5vFEBgyQwGA4yJfgLD7Fp
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…d, not null The three schemas carried 45 nullable fields between them, and the prose said, field by field, that absence and `null` meant the same thing. Each one was a three-state field — absent, `null`, present — modelling a two-state idea, in every SDK generated from this contract, forever, and nothing read the third state: the platform's own patch layer already collapses an explicit `null` to "unchanged" rather than to "clear". All 45 lose the `null` branch and their `default: null`. One survives, and it is named in the prose rather than left to judgement: `schedule`, where §5 permits a *forbidden* field to be written in its own empty form and `null` is an author saying "deliberately none". The rule is stated once, as `COMP-ENV-007` in §2, rather than beside each field, because it is a property of the whole contract rather than of any field in it. §2 is where the envelope already states the rules that hold at every level. Ten conditionals changed shape with it. A branch that said "present but null" — a `CONNECTION` input's `ui`, `generator` and `platformDefault`, a `DERIVED` output's `value`, a `JSON` value's `pattern`, a non-`STRING` value's `format` — now says "not present", which is the `false` schema `BlueprintNode` already uses for `revision` on a repo-local reference. That changes the declared diagnostic from `ERR_INVALID_TYPE` to `ERR_INVALID_VALUE` on seven fixtures, because `ERR_INVALID_VALUE` is what a `false` schema reports and `structural/009` is the precedent for it. `structural/011` keeps `ERR_INVALID_TYPE`, which is the clearest evidence that `schedule` really is the exception and not an oversight. Two `if`s simplified rather than changed: "generator is present and not null" is now just "generator is present". They keep a `properties` entry beside the `required` because Ajv's strictRequired asks for one and `BlueprintNode.if` already answers it that way. Three `effective` pins went with the defaults they pinned. ADR 0008 pins an effective value through a declared `default`, and `format`, `pattern` and `semanticType` no longer have one — an absent field with no default has no effective value, so `structural/040`, `041` and `045` pin the `sensitive: false` they can and no longer claim the rest. That is the one consequence of this change a reader would not predict from the ADR, so it is recorded here. BREAKING CHANGE: a document that writes an optional field as `null` is now rejected, everywhere except `schedule`. ADR 0005 §1's pre-publication window applies: no `v<N>` directory and no migration note, since `git tag -l` is empty and `published.json` records no release. Refs ADR 0007. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5vFEBgyQwGA4yJfgLD7Fp Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…ced resourceType The compatibility tag was a closed seven-member enum — POSTGRES, MYSQL, REDIS, MONGODB, S3_BUCKET, HTTP_SERVICE, SMTP — and ADR 0009 recorded three things wrong with it: it had no scope, six members naming a backing service and one a protocol; it only grew, because GOVERNANCE.md makes adding a term a minor release and removing one a new major; and it could not express anything this repository had not thought of, so a component addressing a service Musher does not offer had to declare nothing, which §4.2 makes acceptable to every consumer. `semanticType` becomes `resourceType` and the enum becomes a grammar: `^[a-z][a-z0-9]*(\.[a-z0-9][a-z0-9-]*)+$`, bounded at 255 characters, lookahead-free so it compiles under RE2 as well as ECMA-262. The grammar is this contract's and the membership is not: §6.3 names `https://api.musher.dev/v1/reference/resource-types` and restates no identifier, which is ADR 0003 §2's rule applied to the surface ADR 0009 §2 designates. The two questions the enum conflated are now two phases. Whether the ends of a wire declare the *same* identifier is two strings and an equality, so it stays `semantic` and stays offline — a consumer and producer that agree on `com.acme.billing.tenant-key` wire cleanly in a client that has never heard of Acme. Whether that identifier is *registered* needs the registry, so it is `capability`, carries the new `ERR_UNKNOWN_RESOURCE_TYPE`, and an offline implementation MUST NOT report it: a grammatical identifier the registry does not name is reserved rather than invalid. That code carries no fixture and says why in the runner's UNCOVERED list. `ERR_INCOMPATIBLE_SEMANTIC_TYPE` becomes `ERR_INCOMPATIBLE_RESOURCE_TYPE`. Renaming a diagnostic is ordinarily forbidden and ADR 0009 argued the exception: the code protects released consumers and there are none, so it is renamed now or it is wrong permanently. Two defects found while implementing it, both fixed here because both are narrowings and both are free only inside the same window. §5.3 defined an authored parameter's schema as component §6.3's block "minus `semanticType`", while §5.1 carried the tag through unchanged on the derived path. So the override path — the path §5.2 pushes an author onto — was the one place the tag silently disappeared, which would have made any consumer keyed on it go dark exactly there. A parameter now carries `resourceType`, and §5.3 compares it with the asymmetry that suits a form rather than a wire: naming none covers an input that names one, because an install form is not where a value acquires a tag, and naming a *different* one is `ERR_INCOMPATIBLE_PARAMETER_ RESOURCE_TYPE`. `semantic/025` pins the direction that is deliberately legal. `BlueprintConnection.fromRole` was a bare string with no grammar, against ADR 0007 §4's rule that every identifier gets one. It takes the node-name DNS label it names. BREAKING CHANGE: `semanticType` is renamed to `resourceType` and its seven enum members are withdrawn in favour of a namespaced grammar, so every document carrying the old field or an unqualified term is rejected; an authored parameter naming a `resourceType` its covered input does not is newly rejected; and `fromRole` is newly constrained to the node-name grammar. ADR 0005 §1's pre-publication window applies: no `v<N>` directory and no migration note, since `git tag -l` is empty and `published.json` records no release. Refs ADR 0009. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5vFEBgyQwGA4yJfgLD7Fp Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
A component is a workload: `ComponentSpec` requires `workload`, and `source` is a pinned image or a git build. So there is no way to write a component this platform does not run, and four of the thirteen curated catalog items cannot function without one — `open-webui` documents the hole in a default that names a sibling service the deployment does not contain. The ADR decides six things, each on its own terms so a maintainer can decline one without unpicking the rest: the sibling `external` block and why not `remote`, `resource`, a `spec`-level tag or a fifth `workload.kind`; the at-least-one-output and no-DERIVED-output rules; `valueFrom: INPUT`; closing blueprint §4.2's `USER`-wiring gap, which §3 depends on; `resourceType` at node scope and why a prefilled address is not sniffed; and `size: null` on a node that runs nothing. The argument the rest hangs on is in §Context, and it is about sets rather than about fields: a service addressed elsewhere is reached with an address, a credential and usually a name, and those values only mean anything together. A node makes the set structural, where three independently tagged inputs would let an address from one provider sit beside a credential from another with nothing in the contract noticing. It is also why this is not the grouping ADR 0018 §3 declined — a node is a local identity the blueprint can see, not a name claiming something about other components' inputs, so §3 is routed around rather than reopened. It records what it rejects, which is most of the value: three tagged inputs and no new node (cheapest, and its deferred half is worth proposing on its own), a typed requirement the installer binds (premature, and additive on top of this), and a fourth family (a category error before it is a cost problem). One decision narrows what validates — a connection may fill only a `CONNECTION` input — and ADR 0005 §1's window is the whole of the opportunity: `git tag -l` is empty, `published.json` records no release, and the three 1.0.0 release pull requests are still open. No mechanical gate detects that narrowing, which the ADR says so a reviewer does not go looking for one. Refs ADR 0009, ADR 0018 §3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5vFEBgyQwGA4yJfgLD7Fp Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
A component was a workload: `ComponentSpec` required `workload`, and `source` was a pinned image or a git build. Four of the thirteen curated catalog items cannot function without a service the platform does not host, and `open-webui` documents the hole in a default that names a sibling service its own deployment does not contain. `spec.external` is the second shape, present instead of `workload`. A component declares exactly one of the two and which key is present is the discriminator, so no document that validates today is rejected: every one has `workload`, and none has `external`, which was `ERR_UNKNOWN_FIELD` until now. §5 becomes "The component's shape" and keeps its anchor, so every fixture clause and every cross-document link still resolves. The block carries one field. An external node has no image, no compute, no endpoints, no health and no address, so `resourceType` — ADR 0009's grammar and ADR 0009's registry, read of the node rather than of a value — is the whole of what it says about itself. What it holds arrives through `contract.inputs` and what it offers leaves through `contract.outputs`, which needed no restructuring because `contract` was already a sibling of `workload`. Four rules keep the shape honest, all structural: at least one output, because a node publishing nothing is a node nothing can need; no `DERIVED` output, because that member means the platform reading a running workload and there is none; and no `platformDefault` or `generator` on an input, which is the pair §6.1 already excludes beside `suppliedBy: CONNECTION`, for reasons that survive the change of setting. §10 records that the `platformDefault` exclusion is about what `SELF_ADDRESS` means rather than about platform defaults as a class, so a second kind is not later read as foreclosed. `valueFrom` gains `INPUT`, with a sibling `input` naming which of the component's own inputs the value is read from. §6.2's invariant is restated in the form it always had a reason to be in: what an output may not read is a value that resolves *after* an edge is bound, which is only a `CONNECTION` input. A `USER` input resolves at form submission — earlier than a `DERIVED` output, which needs a running, addressed workload — so admitting it strengthens nothing and weakens nothing, and blueprint §4.2's legal cycles stay resolvable. The earlier wording was a sufficient condition; this is the necessary one, and the commit says so rather than leaving a reader to wonder whether the rule moved. `ComponentOutput`'s two-branch conditional became three, because with three members the old `else` was no longer exactly `DECLARED` — `INPUT` would have fallen into it and been asked for a literal. Ten structural and three semantic fixtures land with it. `structural/062` is the one a reviewer needs: `ComponentSpec` losing `required: ["workload"]` reads as a relaxation in the diff, and that case proves the obligation moved rather than went away. `structural/067` pins that `valueFrom: INPUT` is not external-only — a workload republishing a value the user gave it is useful and harmless. §11 gains the rule the shape makes necessary: an external node's address is author- or user-supplied, and no phase may resolve it, connect to it, or check that anything answers there. A reachability check against a user-supplied address is a server-side request forgery primitive reachable from a catalog submission, so the silence in §5.6 is a decision and §11 is where the reason lives. Additive: `external`, `input` and the `INPUT` member are new, so no document could have carried them, and every rule above constrains a state no document can currently reach. Refs ADR 0019. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5vFEBgyQwGA4yJfgLD7Fp Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…t run Component §5.6 gives a component a second shape. This is the other end of it: a node that deploys one, and the rules that keep the graph honest about what runs. `size` admits `null`. A node deploying an external component has no Compute Profile to name, and the field stays REQUIRED so absence still means the author forgot — `null` is a declaration and carries no default. §4.3 names this as the third placement ADR 0007 §5 leaves a field nullable, because that section named only two: omission is not available here, so `null` is the only spelling left for a deliberate none. It is the same argument §5 accepts for `schedule`, reached from the opposite direction — that one is a forbidden field written empty, this is a required field with nothing to say. `BP-NODE-002` makes the node and the component agree: `size` is null if and only if the component declares `spec.external`, and both directions are `ERR_CONFLICTING_NODE_COMPUTE` anchored at the node's `size`. One code, because `ERR_CONFLICTING_…` in this contract means two declarations claiming one slot, which is what the node and the component are doing about this node's compute. It is `semantic` rather than `structural` and §4.3 says why: a blueprint cannot see whether its node's component is external, because `componentRef` is a path or a UUID and reading it is `semantic` for one form and `capability` for the other. Making `size` optional and re-imposing it semantically was the alternative, and it would have traded a total offline rule for one that goes silent on a published reference and wanted `ERR_MISSING_FIELD` from a phase it is not registered in. `BP-NODE-003` rejects a `placement` pin beside `size: null`. A pin narrows the hosts a node may be placed on, and a node placed on none has nothing to narrow. `BP-CONN-001` closes §4.2's recorded gap: a connection may fill only a `CONNECTION` input. The gap was recorded rather than decided — "a wire and the install form would then both claim the value, with nothing saying which arrives" — and the timing is forced rather than opportunistic. Component §6.2 now admits an output that reads one of its own inputs, and that is sound only because a `USER` input cannot itself arrive over an edge. Left open, an output could have depended on an inbound connection by way of a wired `USER` input, and the cycles §4.2 deliberately permits would have stopped being resolvable. The corpus was swept and needed nothing: every connection in it already targets a `CONNECTION` input, and the catalog's are all empty. That is worth recording because it is the evidence that this narrowing costs the ecosystem nothing today and would have cost it a major version tomorrow. §5.1 gains a sentence, because it is the half of the feature a reader is most likely to miss: derivation reads a component's inputs rather than its workload, so an external node contributes install-form parameters like any other node. That is what makes the set of values such a node is configured with one form — an address and a credential asked for once, together. §9 records two things rather than leaving them to be discovered: no configured instance is shared across graphs, because §4.2 cannot reach outside the graph it is written in; and a node set that depends on a form answer is foreclosed for an architectural reason, since it would make §5.1's derivation a function of its own output. Six new fixtures. `semantic/026` is the composition the shape exists for, and `semantic/029` is the gap closing. BREAKING CHANGE: a connection whose key names an input that is not `suppliedBy: CONNECTION` is now rejected with `ERR_INPUT_NOT_CONNECTABLE`. Blueprint §4.2 said outright that closing that gap rejects compositions that validate today, so ADR 0005 §1's pre-publication window is the whole of the opportunity: no `v<N>` directory and no migration note, since `git tag -l` is empty and `published.json` records no release. No mechanical gate detects this one — it is a `semantic` rule, so `task changes` and `check:compat` are both blind to it, which is why the declaration is written here by hand. Refs ADR 0019. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5vFEBgyQwGA4yJfgLD7Fp Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
This was referenced Sep 8, 2026
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
A component may now declare
spec.externalin place ofspec.workload: a nodewith a contract that this platform does not run. It is a graph node, so a
blueprint composes it and wires it like any other, and the deploying user is
asked for the values it holds through the install form blueprint §5.1 already
derives. Getting there required clearing the two accepted-but-unimplemented
breaking ADRs first, so this branch also lands ADR 0007 (naming, units,
identifier grammars, and the removal of 45 nullable fields) and ADR 0009
(
semanticTypebecomes a namespacedresourceType).Why
Four of the thirteen curated items in
musher-dev/catalogcannot functionwithout a service the platform does not host, and the catalog documents the hole
itself —
open-webuideclares an input whose default names a sibling serviceits own deployment does not contain. There was no way to say "this composition
includes something addressed elsewhere", and no way for a blueprint to name it.
branch, in its own commit, before any implementation
implemented here. It is a hard prerequisite rather than a companion: the
closed
semanticTypeenum has no term for a credential or a model name, soit could not type this feature's values at all
implemented here
The argument the shape rests on
A service addressed elsewhere is reached with a set of values — an address, a
credential, usually a name selecting what answers — and those values only mean
anything together. Three independently declared inputs can each be satisfied
correctly and still not agree, and nothing in the contract would notice. A node
makes the set structural: a consumer taking two values from one
fromRoleistaking them from one source by construction.
That is also why this is not the grouping ADR 0018 §3 declined. §3 rejected a
declared
groupbecause a group name is a claim about a form that will alsohold other components' inputs, which the declaring document cannot see. A node
is not a name — it is a local identity the blueprint can see, and two nodes'
inputs never merge — so §3 is routed around rather than reopened.
Compatibility
ADR 0005 §1's pre-publication window applies, so no
v<N>directory and nomigration note:
git tag -lis empty,published.jsonrecords no release,.github/release-please/manifest.jsonreads0.0.0for all three families, andPRs #1, #2 and #3 are still open. Maintainer approval is the obligation the
window does not remove, and it is what this PR is asking for.
Every narrowing, declared:
metadata.revision,workload.type,componentRef,required/sensitive/readOnly,placement,sizeGiB, and the rest)nullwithdrawn from 45 fields where absence already meant the same thingsemanticType→resourceType, its seven enum members withdrawn for a namespaced grammarresourceTypecompared where an authored parameter covers an inputfromRolegains the node-name grammarBP-CONN-001— a connection may fill only aCONNECTIONinputThe component half of the external node (1d7a669) is additive:
external,inputand theINPUTmember are new, so no document could have carried them,and every rule attached to them constrains a state no document can reach.
Three things a reviewer should look at rather than take on trust
BP-CONN-001is the one narrowing this feature itself owns, and no gatedetects it. It is a
semanticrule, sotask changesandcheck:compatareboth blind to it — the declaration is written by hand in the commit trailer.
Blueprint §4.2 said outright that closing that gap rejects compositions that
validate today. The corpus and the catalog were swept and needed nothing: every
connection already targets a
CONNECTIONinput, and the catalog's are allempty. It is also not optional —
valueFrom: INPUTis sound only because aUSERinput cannot itself arrive over an edge, or an output could depend on aninbound connection and the cycles §4.2 deliberately permits would stop being
resolvable.
task changeswill readComponentSpeclosingrequired: ["workload"]as arelaxation. It is not one — the
anyOfmoved the obligation rather thanremoved it.
structural/062is the fixture that proves it and exists forexactly this reading.
A defect was found and fixed along the way. Blueprint §5.3 defined an
authored parameter's schema as component §6.3's block "minus
semanticType",while §5.1 carried the tag through on the derived path. So the override path —
the path §5.2 pushes authors onto — was the one place the tag silently
disappeared, which would have made any consumer keyed on it go dark exactly
there. §5.3 now compares it, with the asymmetry that suits a form rather than a
wire: naming none covers an input that names one, and naming a different one
is an error.
Two judgement calls worth ratifying or overruling now
resourceTypecarriesmaxLength: 255, mirroringconfigKey. ADR 0009 fixedthe grammar and no length; a bound is a narrowing, so it is now-or-never.
size: nullis a third surviving nullable placement where ADR 0007 §5named only two. Blueprint §4.3 records it as such with its reason —
sizeisREQUIRED, so omission is not an available spelling — rather than adding it
quietly.
Checklist
task checkpasses locally — 19 checks, 203 conformance cases, 135 testsschemas/dist/regenerated withtask bundleand committed (never edited by hand)clausespec.md— schemadescriptions are explanatory, not normativegit commit -s)Twenty-four new fixtures across the three families.
task site:buildwas run toconfirm the generated
/reference/tree renders the new shapes —resourceTypeand the
INPUTmember both appear — since that tree is generated by one scriptand a shape it cannot read is a build failure rather than a missing page.
Not in this PR
Downstream work is filed rather than done:
musher-dev/platform#2650 (servethe resource-type registry, plus the
scopecolumn ADR 0009 §4 did notanticipate), #2651 (a workload-less
Component, and whyComponentKindmustnot gain a member), #2652 (render a node and a control from
resourceTyperather than from a sniffed hostname), #2653 (mirror the renames);
musher-dev/catalog#24–#27.One caveat is recorded in catalog #25 rather than assumed away: OpenClaw stores
model-provider configuration in its own SQLite via the Control UI rather than
reading it from the environment, so wiring an external node into that item gives
the graph and the shared install form but possibly not automatic configuration.
open-webuitakesOPENAI_API_BASE_URLandOPENAI_API_KEYdirectly andshould land first as the reference item.
🤖 Generated with Claude Code
https://claude.ai/code/session_01J5vFEBgyQwGA4yJfgLD7Fp
On merging
This repository allows squash only, so the PR title becomes the commit on
mainand the six scoped commits on the branch collapse into its body. Twothings follow, and both are why the title reads the way it does:
!, so the squashed commit is marked breaking even if thebody is cleared. GOVERNANCE.md → Compatibility review wants that declaration
in the trailer, and the default squash body — which reproduces all six commit
messages,
BREAKING CHANGE:footers included — is where it lands. Pleasekeep it.
componentrather than left unscoped, because an unscopedfeat:releases nothing. The scope follows PR feat(component)!: let the install form say what control it asks for #79, which squashed acomponent-plus-blueprint branch the same way; release-please attributes by
path, so all three families pick this up from
specifications/*/v1/.