Hand a moved module by its manifest, its build and the machine, never by the vault's reading of its plan, so a node with no plan yet makes the plan that hands it (issue 1966)

The vault judged a module handed only when its assignment already carried a handing plan, and the node-engine followed
that word: a module with no plan was composed the old way, refused for a key only the provider's helper answers
(forge-account's ssh_alias), and so never got its plan. resolution.Handing is now the manifest, its build and the
machine alone; the vault's word is read only to say where the two disagree. A refusal of a file or an own secret the
provisioner's client should fill now says "handed module not handed" and why, never only the keys a binding lacks or a
secret to make.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 02:30:57 +01:00
parent 8e32583089
commit 421e6b0771
7 changed files with 136 additions and 38 deletions
+7 -1
View File
@@ -415,6 +415,11 @@ func resolutionCompose(r mcat.Resolution, with mcat.Rendering, owner map[string]
// family's member (ADR 0283), so the module reads its absence as not given.
continue
}
if why := VaultDisagrees(with, m); sealed == "" && why != "" {
// The vault left it out of what it answered this node, its plan handing it: never a secret to make.
return nil, fmt.Errorf("handed module not handed: %s needs its own secret %q, which the vault answers "+
"its provisioner's client alone, %s (issue 1966)", m.Module, name, why)
}
if sealed == "" {
// Declared and not made. Refused rather than skipped: a module whose own
// credential is silently absent starts, fails to authenticate, and the reason is
@@ -912,7 +917,8 @@ func resolutionCompose(r mcat.Resolution, with mcat.Rendering, owner map[string]
// **Substituted here, not on the machine.** A bound value is not secret — the mesh
// holds it in the clear — so there is nothing for the host to be the only witness of,
// and sending it already filled in means the host learns no new field.
if err := boundInto(copied, known, m.Module, absent); err != nil {
filledHere, handedFile := notHandedWhy(with, m, unsettled)
if err := boundInto(copied, known, m.Module, absent, handedFile, filledHere); err != nil {
return nil, err
}
// And what the module could not have written: the machine it turned out to be