settings: a cluster says what it creates - #39
Merged
Merged
Conversation
ConfigureInstanceProfile had no non-test caller. No flag and no file supplied
a profile, so every create refused with "this agent has no flavor configured;
it cannot create anything until its operator sets one" -- a message naming
something the operator could not act on. Lab validation on the 1cc R630 was
the first thing to notice, because every test that found the profile green
built its own Registry.
The profile becomes a setting in the list slice 1 introduced, so it loads
through the one path and the two layers that catch an unloaded or unwired
setting apply to it. instance-profile.json holds flavor, image and network,
with project optional; it is refused if group- or world-writable and not if
readable, the action level's rule rather than the credential's -- it holds no
secret, but whoever may write it chooses the image, and choosing the image is
choosing what code runs.
Unknown fields are rejected rather than ignored. The likely typo is "flavour",
the spelling this codebase's own prose uses, and ignoring it would leave the
field empty and refuse every create with a message about a flavour the
operator can see in their file.
executorFilled becomes a map from placeholder to the file that supplies it, so
an unconfigured create names instance-profile.json rather than leaving an
operator to find it. {dc} carries no file until cube-cos-api access becomes a
setting, and falls back to the shorter wording rather than inventing a path.
Registry.Profile is ConfigureInstanceProfile's observable counterpart, added
for the reason Writers was: without it a profile on disk reaching the registry
is visible only by creating against a real cloud, which is how this shipped
documented, tested and never called.
Signed-off-by: Travis Wu <travis.wu@bigstack.co>
traviswu-bigstack
force-pushed
the
feat/a-cluster-says-what-it-creates
branch
from
September 12, 2026 04:55
e416d85 to
183bc0c
Compare
traviswu-bigstack
marked this pull request as ready for review
September 12, 2026 04:58
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
The instance profile becomes an operator setting, so
create_instancecan work.ADR 0016 slice 3.
instance-profile.jsonbeside the action level and the credential, holdingflavor,image,networkand an optionalproject. It loads through theconfigurepath slice 1 introduced, which means both of that slice's testlayers apply to it.
Why
Lab validation on the 1cc R630 found
ConfigureInstanceProfilehad nonon-test caller. No flag and no file supplied a profile, so every create
refused with "this agent has no flavor configured; it cannot create anything
until its operator sets one" — a good message naming something an operator
could not act on.
It shipped documented, exercised by tests, and unreachable, because every test
that found it green built its own
Registryand so never asked whetheranything builds one in production. Slice 1 (#38) made that catchable; this is
the first new setting through it.
ADR 0016's own slice table names the test: a written profile reaches the
registry; absent refuses a create naming the file.
Reviewer notes
Mode policy — the action level's rule, not the credential's. Refused if
group- or world-writable, unconstrained on readability. The profile holds no
secret: flavour, image and network are ids of shared cloud resources, and an
operator should be able to read what their own agent creates without root. But
whoever can write it chooses the image, and choosing the image is choosing what
code runs on the instance. Write access is the escalation; read access reveals
nothing.
A partial profile is broken, not unset. Absent is the ordinary state of a
cluster that has not opted in and reports "not configured". A file naming two
of three is something the operator wrote and this agent cannot honour: the
setting is not applied, the startup line says which field is missing, and
creates refuse. ADR 0016's rule — a broken setting disables what it enables and
never more — with the message naming the field rather than the file in general.
Unknown fields are rejected rather than ignored. The likely typo is
flavour, the spelling this codebase's own prose uses. Ignoring it would leaveFlavorempty and refuse every create with a message about a flavour theoperator can see in their own file.
projectis carried but not cross-checked at startup. nova takes theproject from the credential's scope, so the field is a declaration for the
operator, not a value the request sends. A local file-to-file check against the
credential's
projectwould compare two declarations, neither authoritative;the check that matters already exists and is authoritative —
internal/openstackrefuses outright if Keystone reports a different scope. Adding a third statement
to catch a disagreement between the other two is not worth a Keystone call at
boot.
Validation is syntactic at load, semantic at use. Confirming a flavour
exists needs the credential and a network call, which
configuredoes not make.So an operator learns at startup that their file parses, has all three fields
and is not world-writable — and does not learn until their first create that a
uuid does not exist, is in another project, or names a network they cannot
attach. The startup line prints the three values, so a transposed id is at least
visible.
executorFilledbecomes a map from placeholder to the file that suppliesit, so the refusal names
instance-profile.json.{dc}carries no file untilcube-cos-api access becomes a setting (slice 4) and falls back to the shorter
wording rather than inventing a path.
Registry.Profile()isConfigureInstanceProfile's observable counterpart,added for the reason
Writers()was in slice 1: without it, a profile on diskreaching the registry is visible only by creating against a real cloud, so
nothing could assert the wiring without a network — which is how this defect
survived.
Break and restore
settingsapplydroppedThe third break did not fail anything.
TestAnUnconfiguredCreateRefusesNamingTheFileresolves a real
create_instance, and a Post tool's body is a map — so theresolver reaches its placeholders in whatever order Go iterates, and any one of
the three satisfied "the message names the file". Found by breaking it, not by
reading it. Added
TestEveryProfilePlaceholderNamesTheProfileFile, which checkseach placeholder individually and is deterministic; the break then fails it.
Not in this slice
The SaaS-side approval statement still says "flavour, image, network and
project are the cluster's own settings, not chosen by the assistant" rather
than naming values. It is authored in
cube-ai-advisor, which does not know theprofile — the executor holds it. Making it name real values means publishing the
profile to the SaaS, which is a design change, not a rename.
Docs
bigstack-handbook— as-built beside the other slices inkb/cube-ai-advisor/architecture/read-only-plane-end-to-end.md.