From 6bee0b67896ac97523aa0fc9bcac6a14eb4db9d9 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 29 Oct 2026 22:43:16 +0100 Subject: [PATCH] Never set a provision provided without its provider's outcome, read from the entry's state and why, and pin mesh-sdk 692dbf9 (issue 1954) A provider that writes no outcome leaves its keys asked (ADR 0570 clause 3), so a provider that fails is never hidden. --- checks/health/go.mod | 2 +- checks/health/go.sum | 4 +-- modules/mesh-config/go.mod | 2 +- modules/mesh-config/go.sum | 4 +-- modules/mesh-hand-acts/go.mod | 2 +- modules/mesh-hand-acts/go.sum | 4 +-- modules/mesh-healers/go.mod | 2 +- modules/mesh-healers/go.sum | 4 +-- .../cmd/mesh-provisioner/holder.go | 35 ++++++++++++++----- .../cmd/mesh-provisioner/holder_test.go | 10 +++--- modules/mesh-provisioner/go.mod | 2 +- modules/mesh-provisioner/go.sum | 4 +-- modules/mesh-self-check/go.mod | 2 +- modules/mesh-self-check/go.sum | 4 +-- 14 files changed, 51 insertions(+), 30 deletions(-) diff --git a/checks/health/go.mod b/checks/health/go.mod index 13b76586..8c3f521e 100644 --- a/checks/health/go.mod +++ b/checks/health/go.mod @@ -2,4 +2,4 @@ module checks/health go 1.26.0 -require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d +require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 diff --git a/checks/health/go.sum b/checks/health/go.sum index 383c583f..387b26bd 100644 --- a/checks/health/go.sum +++ b/checks/health/go.sum @@ -1,2 +1,2 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d h1:J1QAvzlaIvCCyZGBQX0XGJjYWdNPjLXBF4zaAeIkUOg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 h1:HVKR7kENzltQVkfBN+2zhU9JOPZme7MjDkTvr/oiJ24= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= diff --git a/modules/mesh-config/go.mod b/modules/mesh-config/go.mod index fd98e5ad..e6d2dae0 100644 --- a/modules/mesh-config/go.mod +++ b/modules/mesh-config/go.mod @@ -2,7 +2,7 @@ module mesh-config go 1.26.0 -require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d +require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 require ( github.com/klauspost/compress v1.20.0 // indirect diff --git a/modules/mesh-config/go.sum b/modules/mesh-config/go.sum index 5ed89480..b8d81e43 100644 --- a/modules/mesh-config/go.sum +++ b/modules/mesh-config/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d h1:J1QAvzlaIvCCyZGBQX0XGJjYWdNPjLXBF4zaAeIkUOg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 h1:HVKR7kENzltQVkfBN+2zhU9JOPZme7MjDkTvr/oiJ24= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-hand-acts/go.mod b/modules/mesh-hand-acts/go.mod index 6727bf7c..df0875c3 100644 --- a/modules/mesh-hand-acts/go.mod +++ b/modules/mesh-hand-acts/go.mod @@ -3,7 +3,7 @@ module mesh-hand-acts go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-hand-acts/go.sum b/modules/mesh-hand-acts/go.sum index 326dffd4..a0208db4 100644 --- a/modules/mesh-hand-acts/go.sum +++ b/modules/mesh-hand-acts/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d h1:J1QAvzlaIvCCyZGBQX0XGJjYWdNPjLXBF4zaAeIkUOg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 h1:HVKR7kENzltQVkfBN+2zhU9JOPZme7MjDkTvr/oiJ24= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-healers/go.mod b/modules/mesh-healers/go.mod index e5cbdf2d..6351b3a4 100644 --- a/modules/mesh-healers/go.mod +++ b/modules/mesh-healers/go.mod @@ -3,7 +3,7 @@ module mesh-healers go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-healers/go.sum b/modules/mesh-healers/go.sum index 326dffd4..a0208db4 100644 --- a/modules/mesh-healers/go.sum +++ b/modules/mesh-healers/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d h1:J1QAvzlaIvCCyZGBQX0XGJjYWdNPjLXBF4zaAeIkUOg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 h1:HVKR7kENzltQVkfBN+2zhU9JOPZme7MjDkTvr/oiJ24= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go index 04c2fb3e..b1382a79 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go @@ -12,8 +12,7 @@ package main // - **A key's state comes from its provider's outcome alone** (clause 3, 7): the provider's own entry for that // consumer, `_bound` under the same key: present is provided (the vault's generation carried into the // reference, clause 6), refused is failed with why, waits is waits with what. The holder never sets provided by -// itself, but for a provider that keeps no entries at all (its manifest declares no `bound`), which makes nothing per -// consumer. +// itself: a key of a provider that writes no outcome stays asked. // - **Retiring is asked** (clause 5): a key no longer needed is `retiring`, one approve ask under mesh-provisioner's // name says what goes from which provider on which node; the warrant goes into the key and the provider acts on it; // a decline is kept and not asked again while nothing changes; a key needed again returns to its outcome's state. @@ -292,6 +291,9 @@ func same(a, b provisions.Provision) bool { // entry is a provider's outcome for one consumer, as its entry says it: the provider helper's Entry, read for what // the holder needs and nothing more. type entry struct { + // State and Why are the provider's outcome (provider.Entry, ADR 0570 clause 3): provided, failed or waits. + State string `json:"state,omitempty"` + Why string `json:"why,omitempty"` Refused string `json:"refused,omitempty"` Waits string `json:"waits,omitempty"` VaultName string `json:"vault-name,omitempty"` @@ -299,12 +301,12 @@ type entry struct { } // outcome sets a needed provision's state from its provider's entry: provided, failed or waits; asked while it has -// none. A provider that keeps no entries makes nothing per consumer: provided, said so. +// none. The holder never sets provided by itself (clause 3): a provider whose entries it does not read keeps the key +// asked, said so. func (h *Holder) outcome(key string, p *provisions.Provision) { p.Ask, p.Warrant = "", nil if !keepsEntries(p.Provider.Module) { - p.State, p.Why = provisions.Provided, p.Provider.Module+" makes nothing per consumer: what it provides is its "+ - "placement and served values" + p.State, p.Why = provisions.Asked, p.Provider.Module+" writes no outcome yet: provided once it says it in its own entry" return } e, err := h.Store.Get(boundRead(p.Provider.Module), key) @@ -322,10 +324,17 @@ func (h *Holder) outcome(key string, p *provisions.Provision) { return } switch { - case en.Refused != "": + case en.State == provisions.Failed: + p.State, p.Why = provisions.Failed, orSaid(en.Why, en.Refused, "the provider said it failed") + case en.State == provisions.Waits: + p.State, p.Why = provisions.Waits, orSaid(en.Why, en.Waits, "the provider waits for a person") + case en.State == "" && en.Refused != "": p.State, p.Why = provisions.Failed, en.Refused - case en.Waits != "": + case en.State == "" && en.Waits != "": p.State, p.Why = provisions.Waits, en.Waits + case en.State != "" && en.State != provisions.Provided: + p.State, p.Why = provisions.Asked, "the provider's entry says "+en.State+", which no state of a provision is" + return default: p.State, p.Why = provisions.Provided, "" if en.VaultName != "" || en.Generation > 0 { @@ -349,7 +358,7 @@ func (h *Holder) retired(key string, p provisions.Provision) (bool, string) { return false, "" } if !keepsEntries(p.Provider.Module) { - return true, "its provider keeps nothing for it" + return false, "" } e, err := h.Store.Get(boundRead(p.Provider.Module), key) if err != nil || e != nil { @@ -615,3 +624,13 @@ func (h *Holder) RetireNeeds() (int, error) { } return n, nil } + +// orSaid is the first of the words that says something. +func orSaid(words ...string) string { + for _, w := range words { + if strings.TrimSpace(w) != "" { + return w + } + } + return "" +} diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go index 1164f728..398a338f 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go @@ -108,7 +108,7 @@ func (m *mesh) compose(t *testing.T, node string) Composed { } // A node composes its plans and provisions from what is wanted and placed; a key's state follows its provider's entry -// alone; a provider that keeps no entries makes nothing per consumer; the vault's generation reaches the reference; a +// alone; the vault's generation reaches the reference; a // quiet pass writes nothing (ADR 0570 clauses 1-3, 6, 7; issue 1954 H1, H2). func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) { m := newMesh() @@ -124,7 +124,8 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) { {"composed: asked", "", provisions.Asked, ""}, {"the provider waits for a person", `{"waits":"the database's owner to accept"}`, provisions.Waits, "owner"}, {"the provider refused", `{"refused":"no room on ace"}`, provisions.Failed, "no room"}, - {"the provider made it", `{"vault-name":"pair/gitea-postgres","generation":3}`, provisions.Provided, ""}, + {"the provider says failed in its state", `{"state":"failed","why":"the disk is full"}`, provisions.Failed, "disk"}, + {"the provider made it", `{"state":"provided","vault-name":"pair/gitea-postgres","generation":3}`, provisions.Provided, ""}, } for _, s := range steps { if s.entry != "" { @@ -139,8 +140,9 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) { if p := m.state(t, key); p.Reference == nil || p.Reference.ID != "pair/gitea-postgres" || p.Reference.Generation != 3 { t.Errorf("the vault's generation does not reach the reference: %+v", p.Reference) } - if p := m.state(t, "g14.gitea.split-dns"); p.State != provisions.Provided || !strings.Contains(p.Why, "makes nothing") { - t.Errorf("a provider keeping no entries: %+v", p) + // The holder never sets provided by itself: a provider writing no outcome leaves its key asked (clause 3). + if p := m.state(t, "g14.gitea.split-dns"); p.State != provisions.Asked || !strings.Contains(p.Why, "writes no outcome") { + t.Errorf("a provider writing no outcome: %+v", p) } if e, _ := m.s.Get(PlansState, "g14.gitea"); e == nil { t.Error("the plan is not written") diff --git a/modules/mesh-provisioner/go.mod b/modules/mesh-provisioner/go.mod index 70df969f..b380be43 100644 --- a/modules/mesh-provisioner/go.mod +++ b/modules/mesh-provisioner/go.mod @@ -2,4 +2,4 @@ module mesh-provisioner go 1.26.0 -require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d +require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 diff --git a/modules/mesh-provisioner/go.sum b/modules/mesh-provisioner/go.sum index c728e39b..d2e21659 100644 --- a/modules/mesh-provisioner/go.sum +++ b/modules/mesh-provisioner/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d h1:J1QAvzlaIvCCyZGBQX0XGJjYWdNPjLXBF4zaAeIkUOg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 h1:HVKR7kENzltQVkfBN+2zhU9JOPZme7MjDkTvr/oiJ24= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= diff --git a/modules/mesh-self-check/go.mod b/modules/mesh-self-check/go.mod index 66a0e0d0..fd63c2a8 100644 --- a/modules/mesh-self-check/go.mod +++ b/modules/mesh-self-check/go.mod @@ -3,7 +3,7 @@ module mesh-self-check go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-self-check/go.sum b/modules/mesh-self-check/go.sum index 326dffd4..a0208db4 100644 --- a/modules/mesh-self-check/go.sum +++ b/modules/mesh-self-check/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d h1:J1QAvzlaIvCCyZGBQX0XGJjYWdNPjLXBF4zaAeIkUOg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029213929-66ff33dbd35d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791 h1:HVKR7kENzltQVkfBN+2zhU9JOPZme7MjDkTvr/oiJ24= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214150-692dbf9a1791/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=