A user, bytes, and an archive — because most of what people install is
not a service A shell, a terminal, a chat client, a desktop are a package plus configuration in somebody's home. A mesh with no notion of a user can own /etc and nothing anybody looks at, which is most of the reason to manage a machine at all. Three shapes, and the vocabulary test asserts the count precisely because widening it widens what a compromised control plane can express: user a login, its shell and its groups archive a set of files, fetched by digest and unpacked (file) gains `bytes` for what is not text, and `owner` `user` also makes "zsh is my login shell" declared state. chsh is a command, the link may not carry one, and a shell settable only by hand is a shell the mesh cannot manage. Groups are additive and never pruned — usermod without --append REPLACES them, which would silently remove every group that makes a login able to use the machine. A machine's own groups are not the mesh's to know about. The archive is the one place this host reaches out on its own; everywhere else it holds one outbound connection and fetches nothing. So it carries the discipline the bootstrap already uses for images: pinned by digest, and the digest checked before a single file is written. Two decisions in the unpacker worth naming: - an entry naming a path outside the archive is REFUSED, not sanitised. Rewriting it to land inside would put a file somewhere nobody asked for and report success. Found by the test: the first version quietly relocated it. - symlinks and device nodes are refused rather than skipped, or an archive that needed one arrives silently incomplete. A partial host does archives and refuses users: an archive needs a filesystem and a way to fetch; a user needs a user database it is allowed to write.
This commit is contained in:
@@ -67,9 +67,9 @@ func TestAnUnknownTypeRefusesTheWholeDeclaration(t *testing.T) {
|
||||
func TestAnUnknownFieldIsRefused(t *testing.T) {
|
||||
// A field the host does not know is a thing the control plane believes it asked for.
|
||||
refusal := refusalFor(t, `{"declaration":1,"resources":[
|
||||
{"id":"conf","type":"file","path":"/etc/x","content":"a","owner":"root"}
|
||||
{"id":"conf","type":"file","path":"/etc/x","content":"a","immutable":true}
|
||||
]}`)
|
||||
if !strings.Contains(strings.Join(refusal.Problems, "\n"), "owner") {
|
||||
if !strings.Contains(strings.Join(refusal.Problems, "\n"), "immutable") {
|
||||
t.Errorf("the unknown field was not named: %v", refusal.Problems)
|
||||
}
|
||||
}
|
||||
@@ -240,16 +240,23 @@ func TestAFieldTheNewTypesDoNotUseIsRefused(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheVocabularyIsTheSixShapesTheBootstrapNeeds(t *testing.T) {
|
||||
// novox/hq 07-the-substrate.md names six shapes and the bootstrap uses all of them.
|
||||
// Asserted so that removing one is a failing test rather than a discovery during a
|
||||
// first-node install.
|
||||
func TestTheVocabularyIsTheEightShapesTheMeshNeeds(t *testing.T) {
|
||||
// Six of them the bootstrap uses (novox/hq 07-the-substrate.md), and removing one is a
|
||||
// failing test rather than a discovery during a first-node install.
|
||||
//
|
||||
// Two were added on 2026-08-30 and the count is asserted precisely because adding one is a
|
||||
// decision. `user` and `archive` exist because most of what a person installs is not a
|
||||
// service: a shell, a chat client, a desktop are a package plus configuration **in
|
||||
// somebody's home**, and a mesh with no user can only own /etc. `archive` is for the case
|
||||
// inlining cannot serve — a theme is hundreds of files, and inlining them would rewrite all
|
||||
// of them whenever one changed.
|
||||
speaks := map[Type]bool{}
|
||||
for _, t := range Vocabulary() {
|
||||
speaks[t] = true
|
||||
}
|
||||
for _, want := range []Type{
|
||||
TypeDirectory, TypeFile, TypeService, TypePackage, TypeContainer, TypeAction,
|
||||
TypeUser, TypeArchive,
|
||||
} {
|
||||
if !speaks[want] {
|
||||
t.Errorf("the host no longer speaks %q", want)
|
||||
@@ -258,8 +265,8 @@ func TestTheVocabularyIsTheSixShapesTheBootstrapNeeds(t *testing.T) {
|
||||
t.Errorf("%q is in the vocabulary and cannot be constructed", want)
|
||||
}
|
||||
}
|
||||
if len(speaks) != 6 {
|
||||
t.Errorf("the vocabulary is %d shapes; every addition widens what a compromised "+
|
||||
if len(speaks) != 8 {
|
||||
t.Errorf("the vocabulary is %d shapes rather than 8; every addition widens what a compromised "+
|
||||
"control plane can express, so a change here is a decision: %s",
|
||||
len(speaks), vocabulary())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user