Land feature 57's controller work on its release branch

The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
This commit is contained in:
2026-10-15 21:27:33 +00:00
parent fcdecb4361
commit 97a2d841c7
349 changed files with 42440 additions and 786 deletions
+191 -6
View File
@@ -93,6 +93,15 @@ type Rendering struct {
// makes. Nil on every composition that is sent, and on every other question, which then refuse
// a secret not made with a *NotMadeError.
Foreseen map[string]map[string]bool
// VaultSealed is, for a module on the core's list (CoreCredentialModules), each own secret as the vault sealed it
// to this machine (the mesh's sealed box, as the vault answered it), keyed like Needed (novox/hq ADR 0349 §16,
// issue 755). Given only for a machine whose engine opens the box (VaultBoxContract): its file then carries the
// box and never the controller's own sealed value. Nil everywhere else, and the composition is as it was.
VaultSealed map[string]map[string]json.RawMessage
// NotPlaced is each core module left out of this machine's declaration because the vault gave nothing for its
// credential and no box was kept, with why (issue 755): the machine keeps what it holds of it, and a module not
// yet there is not placed. Nil everywhere else.
NotPlaced map[string]string
// Mesh is every node's address on the private network, which is what a rule saying "from the
// mesh" resolves to. Passed in for the same reason grants are: who else is on the network is
@@ -257,6 +266,13 @@ type Rendering struct {
// contract is link.BusyContract or later). To an older, strict engine the field is not sent, and the
// container is replaced at once, as before.
ReadsBusy bool
// Hands says this machine's node-engine writes a filled file on the provisioner client's word alone (its
// statement's contract is link.HandedContract or later, design 57 P7), and Provisioner is the account the
// provisioner's client runs as here, empty where none runs (novox/hq ADR 0349 §4). With both, a moved module's
// templated files are handed to the client (handed.go); without either, it composes as before.
Hands bool
Provisioner string
}
// RootField is a user resource's field saying the account must never become root without a person
@@ -331,6 +347,15 @@ type Composed struct {
// naming its module and why (novox/hq ADR 0255): that piece is left out and the machine is told
// everything else. Push and plan say it; the merge gate refuses a change that adds one.
Unplaced []string
// HandedOnly is each file left out because only the provisioner's client fills it and the module is not handed on
// this machine (a provider's consumers file, ADR 0373), said once in the plan. Not an unplaced contribution, which
// the merge gate refuses: a module not handed composes as it did.
HandedOnly []string
// Filled is the path of every file handed to the provisioner, in the order composed: the declaration's
// filled files (novox/hq ADR 0349 §4, §5). Plans is each handed module's plan on this machine, by module.
// Both empty where nothing is handed, and the declaration is then what it was.
Filled []string
Plans map[string]Plan
}
// ForeseenSealed is what stands for an own secret a send will make, in a composition asked ahead of
@@ -390,8 +415,9 @@ func (r Resolution) Compose(with Rendering) (Composed, error) {
owner := map[string]string{}
received := map[string]map[string][]Contribution{}
leftOut := map[string]string{}
var foreseen, unplaced []string
resources, err := r.compose(with, owner, received, leftOut, &foreseen, &unplaced)
var foreseen, unplaced, handedOnly []string
handed := &handedFiles{plans: map[string]Plan{}}
resources, err := r.compose(with, owner, received, leftOut, &foreseen, &unplaced, &handedOnly, handed)
if err != nil {
return Composed{}, err
}
@@ -405,8 +431,13 @@ func (r Resolution) Compose(with Rendering) (Composed, error) {
})
}
sort.Strings(unplaced)
return Composed{Resources: resources, Owner: owner, Received: received, LeftOut: leftOut,
Foreseen: foreseen, Unplaced: unplaced}, nil
sort.Strings(handedOnly)
out := Composed{Resources: resources, Owner: owner, Received: received, LeftOut: leftOut,
Foreseen: foreseen, Unplaced: unplaced, HandedOnly: handedOnly, Filled: handed.paths}
if plans := handed.finished(r.Needs); len(plans) > 0 {
out.Plans = plans
}
return out, nil
}
// BusMembershipID names the resource carrying a machine's membership for the new bus, and
@@ -417,7 +448,7 @@ const BusMembershipPath = "/var/lib/mesh/membership-next.json"
func (r Resolution) compose(with Rendering, owner map[string]string,
received map[string]map[string][]Contribution, leftOut map[string]string,
foreseen, unplaced *[]string) ([]map[string]any, error) {
foreseen, unplaced, handedOnly *[]string, handed *handedFiles) ([]map[string]any, error) {
// **A setting is judged where it is stored, and an impossible one costs a module, not a
// machine** (novox/hq ADR 0163, rule 6). A definition that moved under a stored setting makes
// this module uncomposable; it is left out of the declaration — its held things kept, its
@@ -426,6 +457,11 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
left := r.LeftOut(with.Settings, with.Adopted)
kept := make([]Manifest, 0, len(r.Modules))
var stillBackedUp []Manifest
for module, why := range with.NotPlaced {
if _, already := left[module]; !already {
left[module] = why
}
}
for _, m := range r.Modules {
if why, isLeft := left[m.Module]; isLeft {
if leftOut != nil {
@@ -648,7 +684,36 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
"id": NeedID(name), "type": "file", "path": own.Path, "sealed": with.Needed[m.Module][name],
}))
}
// **A handed module's own secrets are the vault's** (design 57 V4, issue 647): each file holding one is
// declared placed and without a value, filled by the machine's provisioner client from the vault; the
// controller makes and seals none of them here.
handsSecrets := with.Handing(m)
for _, name := range sortedKeys(m.OwnSecrets.Plain()) {
if handsSecrets {
owner := m.SecretsOwner
if m.Module == RuntimeModule && r.Account != "" {
owner = r.Account
}
first = append(first, ownedBy(owner, map[string]any{
"id": NeedID(name), "type": "file", "path": m.OwnSecrets[name].Path, HandedSecretField: name,
}))
continue
}
// **A core credential is the vault's box** (ADR 0349 §16, issue 755): carried as the vault sealed it to this
// machine, which the controller cannot open; the controller's own sealed value is not sent with it.
if box, ok := with.vaultSealedOf(m.Module, name); ok {
if string(box) == string(ForeseenVaultBox) {
*foreseen = append(*foreseen, m.Module+"/"+name)
}
owner := m.SecretsOwner
if m.Module == RuntimeModule && r.Account != "" {
owner = r.Account
}
first = append(first, ownedBy(owner, map[string]any{
"id": NeedID(name), "type": "file", "path": m.OwnSecrets[name].Path, VaultSealedField: box,
}))
continue
}
sealed := with.Needed[m.Module][name]
if sealed == "" && with.Foreseen[m.Module][name] {
// Not made, and the next send makes it: composed with a stand-in so that whatever
@@ -718,6 +783,16 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
found = &r.Needs[i]
}
}
// **A credential the vault keeps for this holder alone** (issue 757): where the module is handed, its
// file is declared placed and without a value, and the machine's client fills it from the vault.
if found != nil && found.IssuedToHolder && with.Handing(m) {
first = append(first, ownedBy(m.SecretsOwner, map[string]any{
"id": SecretID(SecretLocal(to, file.Local)), "type": "file", "path": file.Path,
HandedProvisionField: PlanProvision{Name: SecretLocal(to, file.Local), Provision: to,
Local: file.Local, Provider: found.From, ProviderModule: found.Module},
}))
continue
}
if found != nil && found.ByRecord && found.Sealed == "" && !found.Manager {
// Answered by a record whose key has not been supplied since this consumer was
// put on it. **Refused, not skipped.** The mesh discarded the plaintext when the
@@ -837,7 +912,8 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
if err != nil {
return nil, err
}
first = append(first, file)
// Read by the module as its secrets' owner when it names one: the vault, as its own account (issue 723).
first = append(first, ownedBy(m.SecretsOwner, file))
}
if m.Computed != "" {
mine, part := generated[m.Module]
@@ -973,7 +1049,100 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
// ADR 0163, rule 4); judged above, so an invalid one is not here.
keptNetworks, _ := KeptNetworks(m, with.Settings[m.Module], with.Adopted)
// **A moved module's templated files are the provisioner's** (novox/hq ADR 0349 §4, §6, design 57 P6): on a
// machine that can take them, each is declared placed and without its text, and no pass below fills it.
handing, err := handed.begin(r.Node, m, dirs, accessPaths, thisMachine, with, r.Capabilities, r.Needs,
backupPlacer{append(append([]Manifest(nil), r.Modules...), stillBackedUp...), with, thisMachine, r.Capabilities,
unplaced})
if err != nil {
return nil, err
}
// **What the mesh calls the module through each provision it requires** (issue 792): the names its binding
// file carries, composed by the same call, handed to the provider through the plan's entry.
if _, ok := handed.plans[m.Module]; ok && handing != nil {
for _, to := range requirementsContributedTo(m) {
own, err := r.ownNames(m, to, with.Settings[m.Module])
if err != nil {
return nil, err
}
handed.name(m.Module, to, own)
}
}
// A handed collector is filled from items alone: each old form its collections would have held is said
// (design 57 L1).
if plan, ok := handed.plans[m.Module]; ok && handing != nil && len(plan.Collections) > 0 {
*unplaced = append(*unplaced, oldFormsLeftOut(append(append([]Manifest(nil), r.Modules...),
stillBackedUp...), m, plan.Collections)...)
}
for _, unsettled := range resources {
if pr, ok := unsettled[HandedProvisionField].(PlanProvision); ok {
if handing == nil {
return nil, fmt.Errorf("%s: its credential of %s is handed to the provisioner and the module is "+
"not: nothing would fill it", m.Module, pr.Provision)
}
marked := map[string]any{}
for k, v := range unsettled {
marked[k] = v
}
delete(marked, HandedProvisionField)
marked[HandedSecretField] = pr.Name
file, sec, err := handedSecretFile(marked, m, dirs, accessPaths, thisMachine, placed)
if err != nil {
return nil, err
}
file["id"] = m.Module + "." + fmt.Sprint(unsettled["id"])
owner[fmt.Sprint(file["id"])] = m.Module
pr.Path = sec.Path
if err := handed.addProvisionFile(m.Module, pr); err != nil {
return nil, err
}
out = append(out, file)
continue
}
if unsettled[HandedSecretField] != nil {
if handing == nil {
return nil, fmt.Errorf("%s: its own secret %v is handed to the provisioner and the module is "+
"not: nothing would fill it", m.Module, unsettled[HandedSecretField])
}
file, sec, err := handedSecretFile(unsettled, m, dirs, accessPaths, thisMachine, placed)
if err != nil {
return nil, err
}
file["id"] = m.Module + "." + fmt.Sprint(unsettled["id"])
owner[fmt.Sprint(file["id"])] = m.Module
if err := handed.addSecretFile(m.Module, sec); err != nil {
return nil, err
}
out = append(out, file)
continue
}
if handing != nil && fmt.Sprint(unsettled["type"]) == "file" {
if text, _ := unsettled["content"].(string); len(secretsUsed(text)) > 0 &&
!handing[fmt.Sprint(unsettled["id"])] {
return nil, fmt.Errorf("%s: its file %v names a secret in its text, and a handed module's "+
"secrets reach only the files its provisioner client fills: keep it in a template "+
"(design 57 V4)", m.Module, unsettled["id"])
}
}
if handing[fmt.Sprint(unsettled["id"])] {
file, line, err := handedFile(unsettled, m, dirs, accessPaths, thisMachine, placed, with)
if err != nil {
return nil, err
}
file["id"] = m.Module + "." + fmt.Sprint(unsettled["id"])
owner[fmt.Sprint(file["id"])] = m.Module
if err := handed.add(m.Module, line); err != nil {
return nil, err
}
out = append(out, file)
continue
}
if fmt.Sprint(unsettled["type"]) == "file" {
if _, left := consumersHandedOnly(m, unsettled, with); left {
*handedOnly = append(*handedOnly, consumersLeftOutLine(m, unsettled))
continue
}
}
resource, err := ApplySettings(unsettled, with.Settings[m.Module])
if err != nil {
return nil, err
@@ -1202,6 +1371,18 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
}
process["restart-on"] = restarts
}
// **And what a module of its own account is given, its own account reads** (issue 723): the files and
// directories its words name that say no owner, given to the account it runs as, never the operator's.
for _, m := range r.Modules {
if m.RunsAs == "" {
continue
}
w, err := bundleWords(m, with)
if err != nil {
return nil, err
}
givenTo(out, owner, map[string]map[string]string{m.Module: w}, m.RunsAs)
}
}
if with.Adopted {
// First, before anything a module declares: what the mesh needs reachable, then its guard.
@@ -1449,6 +1630,10 @@ type Contribution struct {
// Empty is root, which is what it was and what a module with no bundle and no declared owner
// still wants.
func (r Resolution) provisionsAs(m Manifest) string {
// A module of its own account reads them as that account (ADR 0259 §8, issue 723).
if m.RunsAs != "" {
return m.RunsAs
}
if len(m.Bundles) > 0 && r.Account != "" {
return r.Account
}