feat(blueprint): specify node compute — the size grammar, the Compute Profile catalog, and the advanced pins - #35
Merged
Conversation
Several fields take their value from a controlled vocabulary, and the three families have handled them inconsistently because nothing said how to decide. listing `category` and `lifecycleStage` are closed enums in the schema; blueprint `size` is a length-bounded string with a TODO where its rule should be; the `advanced` pins are free-form strings with no prose at all. ADR 0003 supplies the test, and it is not volatility. It is who decides membership. A term this specification could add or remove by releasing belongs in the schema as an enum. A term that becomes real when something outside this repository is provisioned does not, however stable the list looks today — its grammar stays here as a pattern, its membership is named as an external publication surface, and a value outside it fails in the capability phase. A vocabulary nothing fixes yet gets shape only, with the silence recorded as a gap. The second decision is that a named surface is never a mirrored copy. This repository does not become a vocabulary publisher and GOVERNANCE.md §Scope is not extended. The reasoning is ADR 0001 §6's, applied to data rather than to code: a copy shipped from here acquires the authority of the thing it copies, and the first time the two disagree, implementations will have followed the one that was easier to reach rather than the one that was right. A dated, explicitly non-normative snapshot is rejected for the same reason — a reader who finds a list in the normative repository has already stopped looking, and the date they were counting on to warn them is the one thing they will not check. Refs #19 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…nced pins §4 carried a TODO where the rule for `size` should be, and `advanced` had no prose at all. `size` is REQUIRED on every node and appears in all three examples, so the field a reader meets first was the one this repository defined least. New §4.3 and §4.4 close both, and split each the way §5.1 splits the image-ref rule: the grammar is ours and lives in the schema, the vocabulary is not and does not. **The grammar is three closed segments.** family ∈ general/compute/memory/ storage/gpu/accelerator, tier ∈ economy/standard/performance/premium, size ∈ nano/small/medium/large/xlarge. A value outside them is ERR_INVALID_VALUE in the structural phase, so a typo, a legacy `1vcpu-1gb` raw-resource name, and a versioned `.v1` slug all fail offline rather than on a round trip. Closing the segments is safe in a way closing the offering would not be: an allowlist admitting more is a relaxation and ships in a minor, which is the mirror of §5.1's floating-tag blocklist staying out of the schema because growing a blocklist narrows. **The offering is named, not enumerated.** It is published unauthenticated at https://api.musher.dev/v1/reference/compute-profiles and rendered at https://docs.musher.dev/reference/compute-profiles. A profile becomes available when there is hardware to back it, which is not an event this specification can observe and not one a release of it coincides with — an enum would be wrong in both directions between releases. A slug the grammar admits and the endpoint does not name is reserved rather than available, and naming one is ERR_UNKNOWN_COMPUTE_PROFILE in the capability phase. That is the same line §4.1 already draws for a published component reference, and it costs the same thing: the code goes in UNCOVERED with a reason, because an offline implementation MUST NOT report it. §4.3 also states the consequence of naming a tier rather than a machine — the vCPU and memory behind a slug MAY differ between two deployments of an unchanged document. That is what the abstraction buys, and it should be written down rather than discovered. **§4.4 gives `advanced` shape, not vocabulary.** The nine pins narrow which host may run a node and never change the compute `size` names. A pin term is a lowercase token `^[a-z0-9][a-z0-9_-]*$`, which admits x86_64, arm64, local-nvme, nvlink and cuda; numeric pins are positive integers; array pins mean "any" when empty and may not repeat a term. `cpuDedication` is the one pin whose vocabulary this contract closes, because shared and dedicated are the whole of the concept. The other eight get no vocabulary and no diagnostic, and §4.4 says so plainly rather than implying one. Nothing publishes those terms today — not this repository and not the platform — and whether a pin is satisfiable is not decidable offline. Naming a code for it would claim an implementation reports something none does. They are recorded as gaps, in the form §4.2 already uses, and they take the shape `size` has above once the terms are published. Seven fixtures, 100 → 107 cases, 0 skipped. Four pin the slug grammar, including structural-018, which accepts `gpu.premium.xlarge` — grammatical, not currently offered, and the case that pins the split. Three pin the constraint block. `web-and-database.yaml` now sizes its two nodes differently and carries a `cpuDedication` pin, so both fields appear where a reader will look. Refs #19 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…ublished §10 said two things were unresolvable by a reader outside the platform: "snapshot compute", and Compute Profile slugs like `general.standard.small`. The second is no longer true. Blueprint §4.3 carries the slug grammar and names where the offered profiles are published, so the paragraph now claims a debt that has been paid. Narrowed to the half that remains, with a pointer to §4.3 and ADR 0003 for the half that does not. CLAUDE.md's Known debt section carried the same sentence and gets the same correction; its claim that every spec.md still has TODO sections is also corrected, since component has none and blueprint is down to one. Scoped docs(component) so it cuts no release of its own. Refs #19 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
This was referenced Aug 17, 2026
This was referenced Aug 17, 2026
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
Blueprint §4 carried a
TODOwhere the rule forsizeshould be, and the siblingadvancedblock had no prose at all. Both are now specified in new §4.3 Node compute and §4.4 Advanced constraints, each split the way component §5.1 splits the image-ref rule: the grammar is this contract's and lives in the schema, the vocabulary is not and does not. ADR 0003 records the rule that decided it.Why
Closes #19.
sizeis REQUIRED on every node, appears in all three examples and in nearly every blueprint fixture, and was the field this repository defined least — a bare{type: string, minLength: 1, maxLength: 128}. Component §10 andCLAUDE.mdboth named it as the largest remaining piece of the seeding debt keeping v1 pre-stable.The issue asked three questions. All three have answers in
musher-dev/platform, and this PR takes them:{family}.{tier}.{size}, three closed enums, declared a public contractgpu.inference.largeexample, which is not valid under its own tier enumGET https://api.musher.dev/v1/reference/compute-profiles— "Surface: public. Auth: none (global capability dictionary, cacheable)". Rendered at https://docs.musher.dev/reference/compute-profilescapabilitySELECT 1 FROM compute_profiles WHERE slug = ? AND status = 'ACTIVE'. It needs the catalog, and §6 forbids the earlier phases from reaching the networkBoth URLs were fetched while writing this — the API returns the four ACTIVE
generalprofiles unauthenticated; the docs page returns 200.§4.3 — the grammar is closed, the offering is not
family ∈
general/compute/memory/storage/gpu/accelerator, tier ∈economy/standard/performance/premium, size ∈nano/small/medium/large/xlarge. A value outside them isERR_INVALID_VALUEin thestructuralphase, so a typo, a legacy1vcpu-1gbraw-resource name and a versioned.v1slug all fail offline rather than on a round trip to the platform.Closing the segments is safe in a way closing the offering would not be. An allowlist admitting more is a relaxation and ships in a minor release, so a seventh family later costs nothing. That is the mirror of §5.1, where the floating-tag blocklist stays out of the schema precisely because growing a blocklist narrows. Same principle, opposite sign.
The offering is named, never mirrored. A profile becomes available when the platform has hardware to back it — ADR 0090 reserves ~48 slugs as
DISABLEDand an operator flips one toACTIVEonce a host class is seeded. That is not an event this specification can observe and not one a release of it coincides with, so anenumwould be wrong in both directions between releases: naming tiers that cannot be deployed, rejecting ones that can. A slug the grammar admits and the endpoint does not name is reserved rather than available, and naming one isERR_UNKNOWN_COMPUTE_PROFILE,capability— the same line §4.1 already draws for a published component reference, and it costs the same thing: the code sits inUNCOVEREDwith a reason, because an offline implementation MUST NOT report it.§4.3 also writes down the consequence of naming a tier rather than a machine: the vCPU and memory behind a slug MAY differ between two deployments of an unchanged document. That is what the abstraction buys, and it should be stated rather than discovered.
§4.4 —
advancedgets shape, and its gaps get recordedThe nine pins narrow which host may run a node and never change the compute
sizenames. A pin term is a lowercase token^[a-z0-9][a-z0-9_-]*$(admitsx86_64,arm64,local-nvme,nvlink,cuda); numeric pins are positive integers; array pins mean "any" when empty and may not repeat a term.cpuDedicationis the one pin whose vocabulary this contract closes, becausesharedanddedicatedare the whole of the concept.The other eight get no vocabulary and no diagnostic, and §4.4 says so plainly. Nothing publishes those terms today — not this repository, and not the platform: they are plain
strwith no enum anywhere, five of the nine have no host attribute to match against,accelerator_classesis seeded empty, and platform ADR 0095 (which would make Placement honour them) is accepted but not shipped. InventingERR_UNSATISFIABLE_CONSTRAINTwould claim an implementation reports something none does, and ADR 0002 already rejected parking a rule incapabilityto dodge writing a fixture. They are recorded as gaps in the form §4.2 uses, and they take §4.3's shape once the terms are published.ADR 0003 — the rule, not just this field
The issue anticipated that the vocabulary question might need an ADR, since publishing a controlled vocabulary is arguably a fourth artifact kind. The ADR concludes the opposite and says why. The test is who decides membership, not how volatile the list is:
enumin the schemacategory,lifecycleStagepatternin the schema; membership named as an external surface, checked incapabilitysizeadvancedpinsGOVERNANCE.md§Scope is not extended. A dated, explicitly non-normative snapshot was considered and rejected: it reads as a convenience and behaves as a trap — a reader who finds a list in the normative repository has already stopped looking, and the date meant to warn them is the one thing they will not check.Three things worth a reviewer's attention
1.
structural-018acceptsgpu.premium.xlarge. That fixture exists to be a pass case for a profile nobody can currently deploy. It is the whole argument in one file: grammar is not availability, and a schema that enumerated the offering would reject this document today and accept it next quarter having changed nothing.2. The
conformance/README.mdcoverage table was already stale. It claimed two uncovered codes and listed two, butUNCOVEREDhas held three since #31 —ERR_COMPONENT_NOT_PUBLISHEDwas never added to the prose. Corrected here alongside the new fourth row. The runner's own gate was right; only the hand-written mirror had drifted.3.
maxLengthonsizedrops 128 → 64 andminLengthgoes away. Both are subsumed by the pattern (the longest grammatical slug,accelerator.performance.xlarge, is 30 characters). Keeping a bound alongside a bounded pattern follows thecomponentproperty's precedent rather than being load-bearing.Compatibility
Three narrowings: the
sizepattern, thecpuDedicationenum, and the pin token grammar plusuniqueItems.No new
v<N>directory, and the reason is that v1 has never been published.git tag -lis empty and.github/release-please/manifest.jsonreads0.0.0for all three families. Component §3's guarantee — "a document that validated againstv1.0.0MUST validate against every laterv1.x.y" — has no releasedv1.0.0to run from, so no document anywhere was validated against a published v1 and would now fail.That argument is the one #31 made, and it carries the same expiry: it dies when #1, #2 or #3 merge. If a release PR lands first, this becomes a
v2change.Corpus impact — verified before each narrowing
general.standard.smallis the only profile slug anywhere in the repository (51 occurrences, across examples, spec prose, and both the blueprint and listing fixture trees). It passes the new pattern unchanged.advancedappears in no document in the corpus, so its new grammar breaks nothing.Checklist
task checkpasses locallyschemas/dist/regenerated withtask bundleand committed (never edited by hand)clausespec.md— schemadescriptions are explanatory, not normativegit commit -s)7 new fixtures, 100 → 107 cases, 0 skipped. All 9 examples validate.
ERR_UNKNOWN_COMPUTE_PROFILEis the one new code without a fixture, and it sits inUNCOVEREDwith a reason.🤖 Generated with Claude Code