Skip to content

feat(component)!: a node this platform does not run — ADR 0019, on ADR 0007 and ADR 0009 - #80

Merged
justinmerrell merged 6 commits into
mainfrom
feat/external-component-node
Sep 12, 2026
Merged

justinmerrell merged 6 commits into
mainfrom
feat/external-component-node

Conversation

@justinmerrell

@justinmerrell justinmerrell commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What changes

A component may now declare spec.external in place of spec.workload: a node
with 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
(semanticType becomes a namespaced resourceType).

Why

Four of the thirteen curated items in musher-dev/catalog cannot function
without a service the platform does not host, and the catalog documents the hole
itself — open-webui declares an input whose default names a sibling service
its 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.

  • ADR 0019 — accepted in this
    branch, in its own commit, before any implementation
  • ADR 0009 — accepted 2026-08-19,
    implemented here. It is a hard prerequisite rather than a companion: the
    closed semanticType enum has no term for a credential or a model name, so
    it could not type this feature's values at all
  • ADR 0007 — accepted 2026-08-19,
    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 fromRole is
taking them from one source by construction.

That is also why this is not the grouping ADR 0018 §3 declined. §3 rejected a
declared group because a group name is a claim about a form that will also
hold 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

  • Breaking — a previously valid document now fails.

ADR 0005 §1's pre-publication window applies, so no v<N> directory and no
migration note
: git tag -l is empty, published.json records no release,
.github/release-please/manifest.json reads 0.0.0 for all three families, and
PRs #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:

Change Commit
The ADR 0007 renames (metadata.revision, workload.type, componentRef, required/sensitive/readOnly, placement, sizeGiB, and the rest) 64a7abb
Seven new identifier grammars — input/output/parameter names, connection keys, volume names, build-argument names, listing tags 64a7abb
null withdrawn from 45 fields where absence already meant the same thing f3baefc
semanticTyperesourceType, its seven enum members withdrawn for a namespaced grammar a02b23d
resourceType compared where an authored parameter covers an input a02b23d
fromRole gains the node-name grammar a02b23d
BP-CONN-001 — a connection may fill only a CONNECTION input 1354afb

The component half of the external node (1d7a669) is additive: external,
input and the INPUT member 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-001 is the one narrowing this feature itself owns, and no gate
detects it.
It is a semantic rule, so task changes and check:compat are
both 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 CONNECTION input, and the catalog's are all
empty. It is also not optional — valueFrom: INPUT is sound only because a
USER input cannot itself arrive over an edge, or an output could depend on an
inbound connection and the cycles §4.2 deliberately permits would stop being
resolvable.

task changes will read ComponentSpec losing required: ["workload"] as a
relaxation.
It is not one — the anyOf moved the obligation rather than
removed it. structural/062 is the fixture that proves it and exists for
exactly 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

  • resourceType carries maxLength: 255, mirroring configKey. ADR 0009 fixed
    the grammar and no length; a bound is a narrowing, so it is now-or-never.
  • size: null is a third surviving nullable placement where ADR 0007 §5
    named only two. Blueprint §4.3 records it as such with its reason — size is
    REQUIRED, so omission is not an available spelling — rather than adding it
    quietly.

Checklist

  • task check passes locally — 19 checks, 203 conformance cases, 135 tests
  • schemas/dist/ regenerated with task bundle and committed (never edited by hand)
  • Conformance fixtures added for every behavioural change, each citing a clause
  • Normative prose updated in the affected spec.md — schema descriptions are explanatory, not normative
  • Commit messages are Conventional and correctly scoped (the scope drives release-please)
  • Commits are DCO signed off (git commit -s)

Twenty-four new fixtures across the three families. task site:build was run to
confirm the generated /reference/ tree renders the new shapes — resourceType
and the INPUT member both appear — since that tree is generated by one script
and 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 (serve
the resource-type registry, plus the scope column ADR 0009 §4 did not
anticipate), #2651 (a workload-less Component, and why ComponentKind must
not gain a member), #2652 (render a node and a control from resourceType
rather 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-webui takes OPENAI_API_BASE_URL and OPENAI_API_KEY directly and
should 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
main and the six scoped commits on the branch collapse into its body. Two
things follow, and both are why the title reads the way it does:

  • The title carries !, so the squashed commit is marked breaking even if the
    body 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. Please
    keep it.
  • It is scoped component rather than left unscoped, because an unscoped
    feat: releases nothing. The scope follows PR feat(component)!: let the install form say what control it asks for #79, which squashed a
    component-plus-blueprint branch the same way; release-please attributes by
    path, so all three families pick this up from
    specifications/*/v1/.

justinmerrell and others added 6 commits September 11, 2026 20:30
…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>
@justinmerrell justinmerrell changed the title feat: a node this platform does not run — ADR 0019, on ADR 0007 and ADR 0009 feat(component)!: a node this platform does not run — ADR 0019, on ADR 0007 and ADR 0009 Sep 11, 2026
@justinmerrell
justinmerrell merged commit e69f1ed into main Sep 12, 2026
9 checks passed
@justinmerrell
justinmerrell deleted the feat/external-component-node branch September 12, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant