A held module is no dependence of its neighbours: its hold stops spreading (issue 1126)
mesh/delivery merged: source.main moved to novox/mesh-sdk@394fec8a63c1
mesh/delivery-group group fix/1126-held-as-runs merged: every member is merged
mesh/check pass c2999a4e13cc on d4c61ee8ffd0 | unit 3054 run, 234 kept, 36 skipped | integration 3075 run, 266 kept, 29 skipped | over bound: unit (sl…

novox's seed p-20261028t231141-ec84b4 refused 11 modules "what it composes of ..., held, is not what runs of them":
de-spiegel's hold spread through mailu to postgres, nftables and route-proxy, and from postgres to every consumer, and
gitea, held so, refused the node: "its credential of postgres-database is handed to the provisioner and the module is
not: nothing would fill it". Three rounds each moved the error. The kind: a held module still took part in the
composition as a dependence, so its hold spread.

- The quick fix: let heldAsToday also follow dependsOnHeld's words, and skip the handed refusal for gitea. It moves the
  error once more and leaves every node's applying path holding consumers.
- The thorough fix, chosen: a held module composes only as what runs of it. composeEach no longer compares the
  composition with each held module as its stub (dependsOnHeld removed): a consumer or collector composes against what
  runs of the held module like any other module, and a real difference is still a named difference in the answer, a
  module's own refusal still its refusal. A stub (compose.Stub, catalogue.Manifest.Stubbed) leaves the credentials and
  own secrets it would hand the provisioner out of its plan as they run, never refused for want of a plan. Only what a
  held module's new manifest says to others in their own files is still held behind it (dependents: the provider of
  what it contributes to, the filter where it listens), which heldAsToday already follows.

Tests: rows added to existing tests (a held provider with a binding consumer, which the old code held in the seed's
words; a held feeder of a collector; a held module with a handed credential and own secret). No new test function.
This commit is contained in:
2026-10-29 00:49:00 +01:00
parent d4c61ee8ff
commit c2999a4e13
7 changed files with 100 additions and 96 deletions
+6
View File
@@ -1103,6 +1103,9 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
}
for _, unsettled := range resources {
if pr, ok := unsettled[HandedProvisionField].(PlanProvision); ok {
if handing == nil && m.Stubbed {
continue // held: its credential file is what runs of it, left out (issue 1126)
}
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)
@@ -1127,6 +1130,9 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
continue
}
if unsettled[HandedSecretField] != nil {
if handing == nil && m.Stubbed {
continue // held: its own secret's file is what runs of it, left out (issue 1126)
}
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])
+16
View File
@@ -185,6 +185,22 @@ func TestAHandedModulesCredentialIssuedForItIsNamedInItsPlanAndNoValueIsSealed(t
if !reflect.DeepEqual(c.Plans["bot"].Provisions, want) {
t.Fatalf("the plan names %+v, want %+v", c.Plans["bot"].Provisions, want)
}
// Issue 1126 (novox's seed: "gitea: its credential of postgres-database is handed to the provisioner and the module
// is not"): held, the module is composed as its stub, with no template and no item, so nothing is handed. Its
// credential and its own secret are what runs of it, left out, and the composition is not refused.
r := forgeReader(t, true, true)
for i := range r.Modules {
if r.Modules[i].Module == "bot" {
h := *r.Modules[i].Handed
h.Templates = nil
r.Modules[i].Handed, r.Modules[i].Stubbed, r.Modules[i].Build = &h, true, nil
r.Modules[i].Resources = slices.DeleteFunc(r.Modules[i].Resources, func(x map[string]any) bool { return x["type"] != "directory" })
}
}
c, got = composeWith(t, r, handingTo("store:5000"))
if strings.Contains(got, SecretID("forge-read")) || strings.Contains(got, NeedID("token")) || c.Plans["bot"].Provisions != nil {
t.Fatalf("a held module's handed credential or own secret is composed: %s", got)
}
}
func TestACredentialIssuedForAHolderIsSealedAsBeforeWhereItsModuleIsNotHanded(t *testing.T) {
+5
View File
@@ -870,6 +870,11 @@ type Manifest struct {
// path its manifest leaves to the mesh, a binding). Nil for any other module. Never in a manifest's JSON.
OwnsPaths []string `json:"-"`
// Stubbed marks a module composed as its stub (compose.Stub: held on its node, issue 1126): none of its own
// composition is applied, so what it hands the provisioner (a credential the vault keeps for it, its own secrets)
// is left out of its plan as it runs, never refused for want of a plan. Never in a manifest's JSON.
Stubbed bool `json:"-"`
// Jails are the fail2ban jails this module declares for its own service (novox/hq to-be 31).
// Written into whichever node runs the module, the same way `listens` become that node's rules.
Jails []Jail `json:"jails,omitempty"`