Provision and provider users take the provisioner's client; mesh-vault writes its findings through the conditions client (K-4..K-20, K-21 part; ADR 0634 §4; issue 1966)

This commit is contained in:
2026-11-05 14:26:48 +01:00
parent 7c617bb085
commit e8f93d5db0
45 changed files with 264 additions and 244 deletions
@@ -39,17 +39,17 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// The states it keeps, beside Registry, Needs, Applied and Collect. RefusedState holds, by `<node>.<module>`, why a
// placed module has no plan composed (issue 774): written while it is held, deleted once it composes again.
const (
ProvisionsState = provisions.ProvisionsState
PlansState = provisions.PlansState
ProvisionsState = meshprovisionerclient.ProvisionsState
PlansState = meshprovisionerclient.PlansState
RefusedState = "refused"
)
@@ -169,7 +169,7 @@ func TookSaid(took []Took) string {
return strings.Join(said, ", ")
}
// ComposeNode composes one node: its plans and its provisions.
// ComposeNode composes one node: its plans and its meshprovisionerclient.
func (h *Holder) ComposeNode(ctx context.Context, node string) (Composed, error) {
return h.ComposeNodeWith(ctx, node, false)
}
@@ -250,8 +250,8 @@ func (h *Holder) ComposeNodeWith(ctx context.Context, node string, retireMany bo
out.Problems = append(out.Problems, err.Error())
}
lap("plans written")
needed := map[string]provisions.Provision{}
elsewhere := map[string]provisions.Provision{}
needed := map[string]meshprovisionerclient.Provision{}
elsewhere := map[string]meshprovisionerclient.Provision{}
// ahead are the keys only the newest build of their consumer requires: the plan written, its wanted build's, names
// them not yet, and the provider binds them from the entry they carry (issue 1126).
ahead := map[string]string{}
@@ -259,10 +259,10 @@ func (h *Holder) ComposeNodeWith(ctx context.Context, node string, retireMany bo
for i, plans := range []map[string]catalogue.Plan{atWants, newest} {
for module, plan := range plans {
for _, p := range plan.Provisions {
consumer := provisions.End{Module: module, Node: node}
provider := provisions.End{Module: p.ProviderModule, Node: p.Provider}
pv := provisions.Provision{Provision: p.Provision, Local: p.Local, Consumer: consumer, Provider: provider,
DependsOn: provisions.DependsOn(consumer, p, h.providerPlan(p, node, atWants))}
consumer := meshprovisionerclient.End{Module: module, Node: node}
provider := meshprovisionerclient.End{Module: p.ProviderModule, Node: p.Provider}
pv := meshprovisionerclient.Provision{Provision: p.Provision, Local: p.Local, Consumer: consumer, Provider: provider,
DependsOn: meshprovisionerclient.DependsOn(consumer, p, h.providerPlan(p, node, atWants))}
key, err := pv.Key()
if err != nil {
out.Problems = append(out.Problems, err.Error())
@@ -281,11 +281,11 @@ func (h *Holder) ComposeNodeWith(ctx context.Context, node string, retireMany bo
}
// Its credential is a provision of its own, the vault's (clause 6): on the consumer's node for a value of the
// consumer's own, on the provider's for the provider's one shared value.
ck, ok := provisions.CredentialKey(p.CredentialForm, consumer, provider, p.Provision, p.Local)
ck, ok := meshprovisionerclient.CredentialKey(p.CredentialForm, consumer, provider, p.Provision, p.Local)
if !ok {
continue
}
cn, cm, cp, cl, err := provisions.ParseKey(ck)
cn, cm, cp, cl, err := meshprovisionerclient.ParseKey(ck)
if err != nil {
continue
}
@@ -293,8 +293,8 @@ func (h *Holder) ComposeNodeWith(ctx context.Context, node string, retireMany bo
out.Problems = append(out.Problems, "no node runs mesh-vault, so the credential "+ck+" has no provider")
continue
}
cred := provisions.Provision{Provision: cp, Local: cl, Consumer: provisions.End{Module: cm, Node: cn},
Provider: provisions.End{Module: provisions.VaultModule, Node: vaultNode}}
cred := meshprovisionerclient.Provision{Provision: cp, Local: cl, Consumer: meshprovisionerclient.End{Module: cm, Node: cn},
Provider: meshprovisionerclient.End{Module: meshprovisionerclient.VaultModule, Node: vaultNode}}
if cn == node {
if _, seen := needed[ck]; !seen {
needed[ck] = cred
@@ -329,7 +329,7 @@ func (h *Holder) ComposeNodeWith(ctx context.Context, node string, retireMany bo
func (h *Holder) writeRefused(node string, held map[string]string) error {
var problems []error
for _, module := range sortedKeys(held) {
key, err := provisions.PlanKey(node, module)
key, err := meshprovisionerclient.PlanKey(node, module)
if err != nil {
problems = append(problems, err)
continue
@@ -382,7 +382,7 @@ func (h *Holder) providerPlan(p catalogue.PlanProvision, node string, here map[s
}
return nil
}
key, err := provisions.PlanKey(p.Provider, p.ProviderModule)
key, err := meshprovisionerclient.PlanKey(p.Provider, p.ProviderModule)
if err != nil {
return nil
}
@@ -390,7 +390,7 @@ func (h *Holder) providerPlan(p catalogue.PlanProvision, node string, here map[s
if err != nil || e == nil {
return nil
}
plan, err := provisions.DecodePlan(e.Value)
plan, err := meshprovisionerclient.DecodePlan(e.Value)
if err != nil {
return nil
}
@@ -399,7 +399,7 @@ func (h *Holder) providerPlan(p catalogue.PlanProvision, node string, here map[s
// vaultNode is the node mesh-vault is placed on: the provider of every credential.
func (h *Holder) vaultNode() string {
nodes := h.placedOn(provisions.VaultModule)
nodes := h.placedOn(meshprovisionerclient.VaultModule)
if len(nodes) == 0 {
return ""
}
@@ -421,7 +421,7 @@ func (h *Holder) dependedOn(node string) []string {
if err != nil || e == nil {
continue
}
if p, err := provisions.Decode(e.Value); err == nil {
if p, err := meshprovisionerclient.Decode(e.Value); err == nil {
for _, d := range p.DependsOn {
if strings.HasPrefix(d, node+".") {
out = append(out, d)
@@ -434,13 +434,13 @@ func (h *Holder) dependedOn(node string) []string {
// judgeElsewhere writes each needed key of another node (a provider's shared credential), its state from its outcome;
// that node's own composition keeps it while a provision depends on it.
func (h *Holder) judgeElsewhere(keys map[string]provisions.Provision, out *Composed) {
func (h *Holder) judgeElsewhere(keys map[string]meshprovisionerclient.Provision, out *Composed) {
for _, key := range sortedKeys(keys) {
next := keys[key]
var have provisions.Provision
var have meshprovisionerclient.Provision
e, err := h.Store.Get(ProvisionsState, key)
if err == nil && e != nil {
if have, err = provisions.Decode(e.Value); err == nil {
if have, err = meshprovisionerclient.Decode(e.Value); err == nil {
next.Reference = have.Reference
}
}
@@ -505,12 +505,12 @@ func (h *Holder) runningOn(node string) (map[string]string, error) {
func (h *Holder) writePlans(node string, plans map[string]catalogue.Plan, held map[string]string, out *Composed) error {
var problems []error
for _, module := range sortedKeys(plans) {
key, err := provisions.PlanKey(node, module)
key, err := meshprovisionerclient.PlanKey(node, module)
if err != nil {
problems = append(problems, err)
continue
}
raw, err := provisions.EncodePlan(provisions.Plan{Node: node, Module: module, Plan: plans[module]})
raw, err := meshprovisionerclient.EncodePlan(meshprovisionerclient.Plan{Node: node, Module: module, Plan: plans[module]})
if err != nil {
problems = append(problems, err)
continue
@@ -552,7 +552,7 @@ func (h *Holder) writePlans(node string, plans map[string]catalogue.Plan, held m
// untouched (held on the node, or with a want or a have and no plan read in this pass) that its composition no longer
// names is left as it is: not composed is not unneeded. A pass that would retire most of the node's consumers at once retires none, said as a fault of
// the mesh (issue 940), unless retireMany.
func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision, ahead map[string]string,
func (h *Holder) judgeNode(node string, needed map[string]meshprovisionerclient.Provision, ahead map[string]string,
untouched map[string]string, retireMany bool, out *Composed) {
held := h.held(node)
consumers := map[string]bool{}
@@ -577,7 +577,7 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
var newly []string
leaving := map[string]bool{}
for key, have := range held {
if _, isNeeded := needed[key]; !isNeeded && have.State != provisions.Retiring {
if _, isNeeded := needed[key]; !isNeeded && have.State != meshprovisionerclient.Retiring {
newly = append(newly, key)
leaving[have.Consumer.Module] = true
}
@@ -607,7 +607,7 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
for _, key := range unionKeys(needed, held) {
want, isNeeded := needed[key]
have, isHeld := held[key]
var next provisions.Provision
var next meshprovisionerclient.Provision
switch {
case isNeeded:
next = want
@@ -615,7 +615,7 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
next.Reference = have.Reference
}
h.outcome(key, &next)
if module, isAhead := ahead[key]; isAhead && next.State == provisions.Asked {
if module, isAhead := ahead[key]; isAhead && next.State == meshprovisionerclient.Asked {
// The reason the provider's helper says too: no plan names it yet, and it binds it from this key's entry.
why := "no plan names it yet: the newest build of " + module + " requires it and its wanted build does " +
"not, so its provider binds it from this key's plan entry"
@@ -624,7 +624,7 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
}
next.Why = why
}
case have.State == provisions.Retiring:
case have.State == meshprovisionerclient.Retiring:
next = have
if next.Ask != "" && next.Warrant == nil && !h.askHeld(next) {
out.Problems = append(out.Problems, "the router holds no ask "+next.Ask+" of "+key+": it is raised again")
@@ -643,7 +643,7 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
}
default:
next = have
next.State, next.Why = provisions.Retiring, fmt.Sprintf("no build wanted or placed on %s requires %s of %s on %s "+
next.State, next.Why = meshprovisionerclient.Retiring, fmt.Sprintf("no build wanted or placed on %s requires %s of %s on %s "+
"any more", node, have.Provision, have.Provider.Module, have.Provider.Node)
next.Warrant = nil
h.raise(&next, key, out)
@@ -652,7 +652,7 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
continue
}
next.At = h.Now().UTC()
raw, err := provisions.Encode(next)
raw, err := meshprovisionerclient.Encode(next)
if err != nil {
out.Problems = append(out.Problems, err.Error())
continue
@@ -666,8 +666,8 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
}
// held is the node's provision keys as kept.
func (h *Holder) held(node string) map[string]provisions.Provision {
out := map[string]provisions.Provision{}
func (h *Holder) held(node string) map[string]meshprovisionerclient.Provision {
out := map[string]meshprovisionerclient.Provision{}
keys, err := h.Store.Keys(ProvisionsState)
if err != nil {
return out
@@ -680,7 +680,7 @@ func (h *Holder) held(node string) map[string]provisions.Provision {
if err != nil || e == nil {
continue
}
if p, err := provisions.Decode(e.Value); err == nil {
if p, err := meshprovisionerclient.Decode(e.Value); err == nil {
out[k] = p
}
}
@@ -688,7 +688,7 @@ func (h *Holder) held(node string) map[string]provisions.Provision {
}
// same says two provisions say the same, their time aside.
func same(a, b provisions.Provision) bool {
func same(a, b meshprovisionerclient.Provision) bool {
a.At, b.At, a.V, b.V = time.Time{}, time.Time{}, 0, 0
ra, _ := json.Marshal(a)
rb, _ := json.Marshal(b)
@@ -710,11 +710,11 @@ type entry struct {
// outcome sets a needed provision's state from its provider's entry: provided, failed or waits; asked while it has
// 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) {
func (h *Holder) outcome(key string, p *meshprovisionerclient.Provision) {
p.Ask, p.Warrant = "", nil
keeps, err := h.keepsEntries(p.Provider.Module, p.Provision)
if err != nil {
p.State, p.Why = provisions.Asked, "the manifest of "+p.Provider.Module+" cannot be read: "+err.Error()
p.State, p.Why = meshprovisionerclient.Asked, "the manifest of "+p.Provider.Module+" cannot be read: "+err.Error()
return
}
if !keeps {
@@ -722,40 +722,40 @@ func (h *Holder) outcome(key string, p *provisions.Provision) {
return
}
if !slices.Contains(BoundReads, p.Provider.Module) {
p.State, p.Why = provisions.Asked, p.Provider.Module+" keeps its outcome in its entries, which mesh-provisioner's "+
p.State, p.Why = meshprovisionerclient.Asked, p.Provider.Module+" keeps its outcome in its entries, which mesh-provisioner's "+
"manifest does not read yet"
return
}
e, err := h.Store.Get(boundRead(p.Provider.Module), key)
if err != nil {
p.State, p.Why = provisions.Asked, "the provider's entries cannot be read: "+err.Error()
p.State, p.Why = meshprovisionerclient.Asked, "the provider's entries cannot be read: "+err.Error()
return
}
if e == nil {
p.State, p.Why = provisions.Asked, h.unread(p.Provider.Module)
p.State, p.Why = meshprovisionerclient.Asked, h.unread(p.Provider.Module)
return
}
var en entry
if err := json.Unmarshal(e.Value, &en); err != nil {
p.State, p.Why = provisions.Asked, "the provider's entry is not readable"
p.State, p.Why = meshprovisionerclient.Asked, "the provider's entry is not readable"
return
}
switch {
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 == meshprovisionerclient.Failed:
p.State, p.Why = meshprovisionerclient.Failed, orSaid(en.Why, en.Refused, "the provider said it failed")
case en.State == meshprovisionerclient.Waits:
p.State, p.Why = meshprovisionerclient.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
p.State, p.Why = meshprovisionerclient.Failed, en.Refused
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"
p.State, p.Why = meshprovisionerclient.Waits, en.Waits
case en.State != "" && en.State != meshprovisionerclient.Provided:
p.State, p.Why = meshprovisionerclient.Asked, "the provider's entry says "+en.State+", which no state of a provision is"
return
default:
p.State, p.Why = provisions.Provided, ""
p.State, p.Why = meshprovisionerclient.Provided, ""
if en.VaultName != "" || en.Generation > 0 {
p.Reference = &provisions.Reference{ID: en.VaultName, Generation: en.Generation}
p.Reference = &meshprovisionerclient.Reference{ID: en.VaultName, Generation: en.Generation}
}
}
}
@@ -773,44 +773,44 @@ func (h *Holder) unread(module string) string {
var decl struct {
Reads []string `json:"reads"`
}
if json.Unmarshal([]byte(manifest), &decl) != nil || slices.Contains(decl.Reads, provisions.PlansRead) {
if json.Unmarshal([]byte(manifest), &decl) != nil || slices.Contains(decl.Reads, meshprovisionerclient.PlansRead) {
return ""
}
return module + " reads no plans of the seat (its manifest's reads do not name " + provisions.PlansRead +
return module + " reads no plans of the seat (its manifest's reads do not name " + meshprovisionerclient.PlansRead +
"), so its helper never answers this key"
}
// haveOutcome is the outcome of a provider that makes no credential per consumer: its own have on its node (the lead's
// reading of ADR 0570 clause 3, recorded as an insight). Running with health ok at the build its want names is
// provided; failed is failed with the have's words; anything else is asked, waiting for the provider to run.
func (h *Holder) haveOutcome(p *provisions.Provision) {
func (h *Holder) haveOutcome(p *meshprovisionerclient.Provision) {
where := p.Provider.Module + " on " + p.Provider.Node
// It makes what the consumer needs only once all it depends on is provided (its credential first).
for _, dep := range p.DependsOn {
state := "not composed yet"
if e, err := h.Store.Get(ProvisionsState, dep); err == nil && e != nil {
if d, err := provisions.Decode(e.Value); err == nil {
if d, err := meshprovisionerclient.Decode(e.Value); err == nil {
state = d.State
}
}
if state != provisions.Provided {
p.State, p.Why = provisions.Asked, "waits for "+dep+" ("+state+") before "+where+" can provide it"
if state != meshprovisionerclient.Provided {
p.State, p.Why = meshprovisionerclient.Asked, "waits for "+dep+" ("+state+") before "+where+" can provide it"
return
}
}
key := p.Provider.Node + "." + p.Provider.Module
e, err := h.Store.Get(HaveRead, key)
if err != nil {
p.State, p.Why = provisions.Asked, "the have of "+where+" cannot be read: "+err.Error()
p.State, p.Why = meshprovisionerclient.Asked, "the have of "+where+" cannot be read: "+err.Error()
return
}
if e == nil {
p.State, p.Why = provisions.Asked, "waits for "+where+" to run: it says nothing yet"
p.State, p.Why = meshprovisionerclient.Asked, "waits for "+where+" to run: it says nothing yet"
return
}
have, err := delivery.DecodeHave(e.Value)
if err != nil {
p.State, p.Why = provisions.Asked, "the have of "+where+" is not readable"
p.State, p.Why = meshprovisionerclient.Asked, "the have of "+where+" is not readable"
return
}
wantFP := ""
@@ -821,19 +821,19 @@ func (h *Holder) haveOutcome(p *provisions.Provision) {
}
switch {
case have.Phase == delivery.PhaseFailed || have.Health == delivery.HealthFailed:
p.State, p.Why = provisions.Failed, orSaid(have.Detail, where+" failed")
p.State, p.Why = meshprovisionerclient.Failed, orSaid(have.Detail, where+" failed")
case have.Phase == delivery.PhaseRunning && have.Health == delivery.HealthOK && (wantFP == "" || have.FP == wantFP):
// A provider's have counts as its outcome only from a build whose manifest offers the provision (ADR 0603 clause 5).
// Refused only when that manifest is known and does not offer it: a have with no builder key (an old-form build, an
// adopted module) or an unreadable manifest keeps the outcome it had.
if offers, known := h.buildOffers(p.Provider.Module, have.FP, p.Provision); known && !offers {
p.State, p.Why = provisions.Asked, "waits for "+where+" to run a build that offers "+p.Provision+": the build it runs ("+
p.State, p.Why = meshprovisionerclient.Asked, "waits for "+where+" to run a build that offers "+p.Provision+": the build it runs ("+
have.FP+") does not"
return
}
p.State, p.Why = provisions.Provided, ""
p.State, p.Why = meshprovisionerclient.Provided, ""
default:
p.State, p.Why = provisions.Asked, fmt.Sprintf("waits for %s to run healthy at the build its want names (it is %s, "+
p.State, p.Why = meshprovisionerclient.Asked, fmt.Sprintf("waits for %s to run healthy at the build its want names (it is %s, "+
"health %s)", where, have.Phase, have.Health)
}
}
@@ -975,7 +975,7 @@ func (h *Holder) providerManifest(module string) (string, error) {
}
// retired says a retiring key the operator approved is retired: its provider's entry is gone.
func (h *Holder) retired(key string, p provisions.Provision) (bool, string) {
func (h *Holder) retired(key string, p meshprovisionerclient.Provision) (bool, string) {
if p.Warrant == nil || p.Warrant.Option != OptionApprove {
return false, ""
}
@@ -1003,7 +1003,7 @@ const (
const askLasts = occ.ApproveLasts - 10*time.Minute
// retiringAsk is the ask of one retiring provision, composed from the key alone.
func retiringAsk(key string, p provisions.Provision, at time.Time, id string) oc.Ask {
func retiringAsk(key string, p meshprovisionerclient.Provision, at time.Time, id string) oc.Ask {
what := fmt.Sprintf("%s's access to %s on %s, for %s on %s", p.Consumer.Module, p.Provision, p.Provider.Node,
p.Consumer.Module, p.Consumer.Node)
a := oc.Ask{ID: id, Headline: "Remove " + p.Consumer.Module + "'s " + p.Provision + " access?",
@@ -1011,7 +1011,7 @@ func retiringAsk(key string, p provisions.Provision, at time.Time, id string) oc
"is handed to the resource's owner and its credential goes; the resource and its data stay.", p.Consumer.Node,
p.Provision, p.Provider.Module, what),
Who: occ.Operator, Expires: occ.FormatTime(at.Add(askLasts)), OnExpiry: "nothing is removed; it is asked again when it changes",
About: "provision." + key, Details: "The provision " + key + " of mesh-provisioner's state provisions."}
About: "provision." + key, Details: "The provision " + key + " of mesh-provisioner's state meshprovisionerclient."}
for _, o := range []struct{ id, label, does string }{
{OptionApprove, "Remove", "the provider removes the credential; the resource and its data stay"},
{OptionDecline, "Keep", "nothing is removed, and it is not asked again while nothing changes"},
@@ -1041,7 +1041,7 @@ const askWaits = "its ask waits until fewer than 3 of mesh-provisioner's asks st
var errAskWaits = errors.New(askWaits)
// raise asks a retiring key and says so in out: asked, waiting for an open ask to end, or a problem.
func (h *Holder) raise(p *provisions.Provision, key string, out *Composed) {
func (h *Holder) raise(p *meshprovisionerclient.Provision, key string, out *Composed) {
switch err := h.ask(p, key); {
case err == nil:
out.Asked = append(out.Asked, key)
@@ -1053,7 +1053,7 @@ func (h *Holder) raise(p *provisions.Provision, key string, out *Composed) {
// ask raises the retiring ask of a key, under mesh-provisioner's own name; one already raised stands. While
// askOpenAtMost asks stand open it raises none and the key's why says so.
func (h *Holder) ask(p *provisions.Provision, key string) error {
func (h *Holder) ask(p *meshprovisionerclient.Provision, key string) error {
if p.Ask != "" {
return nil
}
@@ -1086,7 +1086,7 @@ func (h *Holder) openAsks() int {
keys, _ := h.Store.Keys(ProvisionsState)
for _, k := range keys {
if e, err := h.Store.Get(ProvisionsState, k); err == nil && e != nil {
if p, err := provisions.Decode(e.Value); err == nil && p.State == provisions.Retiring && p.Ask != "" &&
if p, err := meshprovisionerclient.Decode(e.Value); err == nil && p.State == meshprovisionerclient.Retiring && p.Ask != "" &&
p.Warrant == nil {
n++
}
@@ -1097,7 +1097,7 @@ func (h *Holder) openAsks() int {
// askHeld says the router holds a key's ask, or may not have taken it yet: false only when it has no record of it
// askHeldWithin after it was raised (issue 940: an ask the router refused was counted asked for good).
func (h *Holder) askHeld(p provisions.Provision) bool {
func (h *Holder) askHeld(p meshprovisionerclient.Provision) bool {
if h.Asks == nil || p.Ask == "" {
return true
}
@@ -1125,8 +1125,8 @@ func (h *Holder) Decide() []string {
if err != nil || e == nil {
continue
}
p, err := provisions.Decode(e.Value)
if err != nil || p.State != provisions.Retiring || p.Ask == "" || p.Warrant != nil {
p, err := meshprovisionerclient.Decode(e.Value)
if err != nil || p.State != meshprovisionerclient.Retiring || p.Ask == "" || p.Warrant != nil {
continue
}
raw, found, err := h.Asks.Record(p.Ask)
@@ -1180,7 +1180,7 @@ func (h *Holder) Decide() []string {
said = append(said, k+": the answer is refused: its time is not RFC 3339 ("+err.Error()+")")
continue
}
p.Warrant = &provisions.Warrant{Ask: p.Ask, Option: option, By: by, At: answeredAt.UTC()}
p.Warrant = &meshprovisionerclient.Warrant{Ask: p.Ask, Option: option, By: by, At: answeredAt.UTC()}
h.put(k, p)
said = append(said, k+": "+option+" by "+by)
}
@@ -1200,8 +1200,8 @@ func (h *Holder) raiseWaiting() []string {
if err != nil || e == nil {
continue
}
p, err := provisions.Decode(e.Value)
if err != nil || p.State != provisions.Retiring || p.Ask != "" || p.Warrant != nil || !strings.Contains(p.Why, askWaits) {
p, err := meshprovisionerclient.Decode(e.Value)
if err != nil || p.State != meshprovisionerclient.Retiring || p.Ask != "" || p.Warrant != nil || !strings.Contains(p.Why, askWaits) {
continue
}
switch err := h.ask(&p, k); {
@@ -1297,9 +1297,9 @@ func (h *Holder) LastingHolds() map[string]string {
return out
}
func (h *Holder) put(key string, p provisions.Provision) {
func (h *Holder) put(key string, p meshprovisionerclient.Provision) {
p.At = h.Now().UTC()
if raw, err := provisions.Encode(p); err == nil {
if raw, err := meshprovisionerclient.Encode(p); err == nil {
if err := h.Store.Put(ProvisionsState, key, json.RawMessage(raw)); err != nil {
h.Logf("[%s] %s is not written: %v", Module, key, err)
}
@@ -1334,7 +1334,7 @@ func (h *Holder) NodesOfModule(module string) []string {
if keys, err := h.Store.Keys(ProvisionsState); err == nil {
for _, k := range keys {
if e, err := h.Store.Get(ProvisionsState, k); err == nil && e != nil {
if p, err := provisions.Decode(e.Value); err == nil && p.Provider.Module == module {
if p, err := meshprovisionerclient.Decode(e.Value); err == nil && p.Provider.Module == module {
nodes[p.Consumer.Node] = true
}
}
@@ -1368,7 +1368,7 @@ func (h *Holder) placedOn(module string) []string {
// NodeOfEntry is the consumer's node a provider's entry key names.
func NodeOfEntry(key string) []string {
if n, _, _, _, err := provisions.ParseKey(key); err == nil {
if n, _, _, _, err := meshprovisionerclient.ParseKey(key); err == nil {
return []string{n}
}
return nil
@@ -1403,11 +1403,11 @@ func (h *Holder) Rejudge(key string) (string, error) {
if err != nil || e == nil {
return "", err
}
have, err := provisions.Decode(e.Value)
have, err := meshprovisionerclient.Decode(e.Value)
if err != nil {
return "", err
}
if have.State == provisions.Retiring {
if have.State == meshprovisionerclient.Retiring {
if gone, why := h.retired(key, have); gone {
return key + " retired: " + why, h.Store.Delete(ProvisionsState, key)
}
@@ -22,9 +22,9 @@ import (
"github.com/nats-io/nats.go/jetstream"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/provisions"
"git.novox.be/novox/mesh-sdk/go/testbus"
)
@@ -132,9 +132,9 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
published(t, mem, c)
vaultManifest := `{"module":"mesh-vault","version":"1","state":["bound"]}` // the vault keeps its outcomes as entries
vaultDigest := delivery.ManifestDigest([]byte(vaultManifest))
vaultValue, _ := delivery.EncodeManifest(delivery.Manifest{Module: provisions.VaultModule, JSON: vaultManifest})
vaultValue, _ := delivery.EncodeManifest(delivery.Manifest{Module: meshprovisionerclient.VaultModule, JSON: vaultManifest})
mem.write(t, ManifestsRead, mustKey(delivery.ManifestsKey(vaultDigest)), string(vaultValue))
mem.write(t, ModulesRead, provisions.VaultModule, `{"v":1,"none2":"the test's","manifest":"`+vaultDigest+`"}`)
mem.write(t, ModulesRead, meshprovisionerclient.VaultModule, `{"v":1,"none2":"the test's","manifest":"`+vaultDigest+`"}`)
for state, keys := range mem.states {
for key, raw := range keys {
if err := s.Put(state, key, json.RawMessage(raw)); err != nil {
@@ -195,19 +195,19 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
a := &asked{}
h := &Holder{Store: Watching(s, ws), Asks: a, Now: time.Now, Logf: t.Logf}
h.Compose = FromCompositions(h.Store)
state := func(key string) provisions.Provision {
state := func(key string) meshprovisionerclient.Provision {
e, _ := s.Get(ProvisionsState, key)
if e == nil {
return provisions.Provision{}
return meshprovisionerclient.Provision{}
}
p, _ := provisions.Decode(e.Value)
p, _ := meshprovisionerclient.Decode(e.Value)
return p
}
key := node + ".restic.postgres-database"
if _, err := h.ComposeNode(ctx, node); err != nil {
t.Fatal(err)
}
if p := state(key); p.State != provisions.Asked || p.Provider.Module != "postgres" {
if p := state(key); p.State != meshprovisionerclient.Asked || p.Provider.Module != "postgres" {
t.Fatalf("the placement and the want composed %+v", p)
}
if e, _ := s.Get(PlansState, node+".restic"); e == nil {
@@ -215,7 +215,7 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
}
// The providers' outcomes: the vault provides the credential where restic has one, postgres runs healthy.
for _, dep := range state(key).DependsOn {
if err := s.Put(boundRead(provisions.VaultModule), dep, map[string]any{"state": "provided"}); err != nil {
if err := s.Put(boundRead(meshprovisionerclient.VaultModule), dep, map[string]any{"state": "provided"}); err != nil {
t.Fatal(err)
}
}
@@ -248,7 +248,7 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
t.Fatal(err)
}
}
if p := state(key); p.State != provisions.Asked {
if p := state(key); p.State != meshprovisionerclient.Asked {
t.Fatalf("a build not offering the provision turned it %s", p.State)
}
offered(`["postgres-database"]`)
@@ -257,7 +257,7 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
t.Fatal(err)
}
}
if p := state(key); p.State != provisions.Provided {
if p := state(key); p.State != meshprovisionerclient.Provided {
t.Fatalf("the outcomes did not turn it provided: %+v", p)
}
// restic leaves the node and its want goes: retiring, one ask. Its compositions stay until its engine deletes them.
@@ -273,7 +273,7 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
if _, err := h.ComposeNode(ctx, node); err != nil {
t.Fatal(err)
}
if r := state(key); r.State != provisions.Retiring || r.Ask == "" || len(a.ids) == 0 {
if r := state(key); r.State != meshprovisionerclient.Retiring || r.Ask == "" || len(a.ids) == 0 {
t.Fatalf("removing the want did not ask: %+v, asked %v", r, a.ids)
}
}
@@ -15,10 +15,10 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// router is the operator's channel as the holder asks on it: it holds an ask open, or, like the router past its open
@@ -114,13 +114,13 @@ var (
split = catalogue.PlanProvision{Provision: "split-dns", Provider: "g14", ProviderModule: "systemd-resolved"}
)
func (m *mesh) state(t *testing.T, key string) provisions.Provision {
func (m *mesh) state(t *testing.T, key string) meshprovisionerclient.Provision {
t.Helper()
e, _ := m.s.Get(ProvisionsState, key)
if e == nil {
return provisions.Provision{}
return meshprovisionerclient.Provision{}
}
p, err := provisions.Decode(e.Value)
p, err := meshprovisionerclient.Decode(e.Value)
if err != nil {
t.Fatal(err)
}
@@ -150,11 +150,11 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
state string
why string
}{
{"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 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, ""},
{"composed: asked", "", meshprovisionerclient.Asked, ""},
{"the provider waits for a person", `{"waits":"the database's owner to accept"}`, meshprovisionerclient.Waits, "owner"},
{"the provider refused", `{"refused":"no room on ace"}`, meshprovisionerclient.Failed, "no room"},
{"the provider says failed in its state", `{"state":"failed","why":"the disk is full"}`, meshprovisionerclient.Failed, "disk"},
{"the provider made it", `{"state":"provided","vault-name":"pair/gitea-postgres","generation":3}`, meshprovisionerclient.Provided, ""},
}
for _, s := range steps {
if s.entry != "" {
@@ -173,11 +173,11 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
// holder alone.
split := "g14.gitea.split-dns"
for _, row := range []struct{ have, want, state, why string }{
{"", "", provisions.Asked, "says nothing yet"},
{`{"v":1,"fp":"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","phase":"applying","health":"unknown"}`, "", provisions.Asked, "applying"},
{`{"v":1,"fp":"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","phase":"running","health":"ok"}`, `{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","self":true}`, provisions.Asked, "build its want"},
{`{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","phase":"failed","health":"failed","detail":"the unit did not start"}`, "", provisions.Failed, "did not start"},
{`{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","phase":"running","health":"ok"}`, `{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","self":true}`, provisions.Provided, ""},
{"", "", meshprovisionerclient.Asked, "says nothing yet"},
{`{"v":1,"fp":"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","phase":"applying","health":"unknown"}`, "", meshprovisionerclient.Asked, "applying"},
{`{"v":1,"fp":"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","phase":"running","health":"ok"}`, `{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","self":true}`, meshprovisionerclient.Asked, "build its want"},
{`{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","phase":"failed","health":"failed","detail":"the unit did not start"}`, "", meshprovisionerclient.Failed, "did not start"},
{`{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","phase":"running","health":"ok"}`, `{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","self":true}`, meshprovisionerclient.Provided, ""},
} {
if row.have != "" {
m.s.write(t, HaveRead, "g14.systemd-resolved", row.have)
@@ -195,8 +195,8 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
bfp := "src2:" + strings.Repeat("bb", 32)
buildKey := mustKey(delivery.BuilderBuildKey("systemd-resolved", bfp))
for _, row := range []struct{ name, provides, state, why string }{
{"a build whose manifest does not offer it", `["other"]`, provisions.Asked, "offers split-dns"},
{"a build whose manifest offers it", `[{"name":"split-dns"}]`, provisions.Provided, ""},
{"a build whose manifest does not offer it", `["other"]`, meshprovisionerclient.Asked, "offers split-dns"},
{"a build whose manifest offers it", `[{"name":"split-dns"}]`, meshprovisionerclient.Provided, ""},
} {
raw := `{"module":"systemd-resolved","version":"1","provides":` + row.provides + `}`
digest := delivery.ManifestDigest([]byte(raw))
@@ -238,7 +238,7 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
c.atNewest["g14"] = c.atWant["g14"]
c.compose(t, "g14")
cred := c.state(t, "g14.gitea.secret-postgres-database")
if cred.Provider.Module != "mesh-vault" || cred.Provider.Node != "novox" || cred.State != provisions.Asked {
if cred.Provider.Module != "mesh-vault" || cred.Provider.Node != "novox" || cred.State != meshprovisionerclient.Asked {
t.Errorf("the credential's provision is %+v", cred)
}
if d := c.state(t, key).DependsOn; !slices.Equal(d, []string{"g14.gitea.secret-postgres-database"}) {
@@ -254,9 +254,9 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
for _, row := range []struct {
name, entry, cred, state string
}{
{"its credential still asked", "", provisions.Asked, provisions.Asked},
{"its credential still asked", "", meshprovisionerclient.Asked, meshprovisionerclient.Asked},
{"its credential provided by the vault", `{"state":"provided","vault-name":"pair/g14/s3-bucket/g14/gitea","generation":1}`,
provisions.Provided, provisions.Provided},
meshprovisionerclient.Provided, meshprovisionerclient.Provided},
} {
if row.entry != "" {
c.s.write(t, "mesh-vault.bound", s3cred, row.entry)
@@ -270,7 +270,7 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
// An entry's change judges its key alone, no composition.
before := len(m.composed)
m.s.write(t, "postgres.bound", key, `{"refused":"the login is locked"}`)
if said, err := m.h.Rejudge(key); err != nil || !strings.Contains(said, provisions.Failed) || len(m.composed) != before {
if said, err := m.h.Rejudge(key); err != nil || !strings.Contains(said, meshprovisionerclient.Failed) || len(m.composed) != before {
t.Errorf("rejudged %q, %v, composed %d more", said, err, len(m.composed)-before)
}
}
@@ -293,17 +293,17 @@ func TestRetiringIsAskedAndActsOnlyOnTheWarrant(t *testing.T) {
removed bool
}{
{"a key composed ahead of its want is asked, not retiring", func() {}, nil,
map[string]string{pg: provisions.Provided, kc: provisions.Asked}, false},
map[string]string{pg: meshprovisionerclient.Provided, kc: meshprovisionerclient.Asked}, false},
{"one requirement removed and another added: only the removed retires", func() {
m.atWant["g14"] = map[string]catalogue.Plan{"gitea": planOf(oidc)}
m.atNewest["g14"] = map[string]catalogue.Plan{"gitea": planOf(oidc)}
}, []string{pg}, map[string]string{pg: provisions.Retiring, kc: provisions.Asked}, false},
{"composed again: no second ask", func() {}, nil, map[string]string{pg: provisions.Retiring}, false},
}, []string{pg}, map[string]string{pg: meshprovisionerclient.Retiring, kc: meshprovisionerclient.Asked}, false},
{"composed again: no second ask", func() {}, nil, map[string]string{pg: meshprovisionerclient.Retiring}, false},
{"needed again before the warrant: back to its outcome", func() {
m.atWant["g14"] = map[string]catalogue.Plan{"gitea": planOf(oidc, pgDB)}
}, nil, map[string]string{pg: provisions.Provided}, false},
}, nil, map[string]string{pg: meshprovisionerclient.Provided}, false},
{"gone again: asked again", func() { m.atWant["g14"] = map[string]catalogue.Plan{"gitea": planOf(oidc)} },
[]string{pg}, map[string]string{pg: provisions.Retiring}, false},
[]string{pg}, map[string]string{pg: meshprovisionerclient.Retiring}, false},
}
for _, s := range steps {
s.change()
@@ -336,7 +336,7 @@ func TestRetiringIsAskedAndActsOnlyOnTheWarrant(t *testing.T) {
if w := m.state(t, pg).Warrant; w.Option != "yes" {
t.Fatalf("the warrant says %q, and a provider lets go only on \"yes\"", w.Option)
}
if said, _ := m.h.Rejudge(pg); said != "" || m.state(t, pg).State != provisions.Retiring {
if said, _ := m.h.Rejudge(pg); said != "" || m.state(t, pg).State != meshprovisionerclient.Retiring {
t.Errorf("retired while the provider's entry stands: %q", said)
}
_ = m.s.Delete("postgres.bound", pg)
@@ -397,7 +397,7 @@ func TestOneBadModuleDoesNotDropItsNode(t *testing.T) {
if e, _ := m.s.Get(PlansState, "g14.nats"); e == nil {
t.Error("the held module's last plan was removed")
}
if p := m.state(t, "g14.nats.oidc-client"); p.State != provisions.Asked {
if p := m.state(t, "g14.nats.oidc-client"); p.State != meshprovisionerclient.Asked {
t.Errorf("the held module's provision is %s (%s), not kept", p.State, p.Why)
}
got, err := m.h.read(RefusedState, "", "g14", "")
@@ -539,21 +539,21 @@ func TestAProvidersOutcomeIsReadPerOffer(t *testing.T) {
}{
{"gitea-read: the helper's refusal is not its outcome; gitea runs, so provided", "ace",
map[string]catalogue.Plan{"claude-code": planOf(catalogue.PlanProvision{Provision: "gitea-read", Provider: "novox", ProviderModule: "gitea"})},
"ace.claude-code.gitea-read", "gitea.bound", refused, "novox.gitea", provisions.Provided, ""},
"ace.claude-code.gitea-read", "gitea.bound", refused, "novox.gitea", meshprovisionerclient.Provided, ""},
{"forge-account: still the helper's entry", "ace",
map[string]catalogue.Plan{"mesh-build-go": planOf(catalogue.PlanProvision{Provision: "forge-account", Provider: "novox", ProviderModule: "gitea"})},
"ace.mesh-build-go.forge-account", "gitea.bound", `{"state":"provided"}`, "", provisions.Provided, ""},
"ace.mesh-build-go.forge-account", "gitea.bound", `{"state":"provided"}`, "", meshprovisionerclient.Provided, ""},
{"route: route-proxy's entry is read", "novox",
map[string]catalogue.Plan{"gitea": planOf(catalogue.PlanProvision{Provision: "route", Provider: "novox", ProviderModule: "route-proxy"})},
"novox.gitea.route", "route-proxy.bound", `{"state":"provided"}`, "", provisions.Provided, ""},
"novox.gitea.route", "route-proxy.bound", `{"state":"provided"}`, "", meshprovisionerclient.Provided, ""},
{"mcp-endpoint: node-tools' entry is read", "ace",
map[string]catalogue.Plan{"claude-code": planOf(catalogue.PlanProvision{Provision: "mcp-endpoint", Provider: "ace", ProviderModule: "node-tools"})},
"ace.claude-code.mcp-endpoint", "node-tools.bound", `{"state":"provided"}`, "", provisions.Provided, ""},
"ace.claude-code.mcp-endpoint", "node-tools.bound", `{"state":"provided"}`, "", meshprovisionerclient.Provided, ""},
// Issue 774, 2026-10-31: route-proxy's helper read the controller's assignments, never the seat's plans, so 38
// routes the seat keys stood asked with no why while the assigner withheld their consumers' builds.
{"route: no entry from a provider that reads no plans of the seat is said so", "ace",
map[string]catalogue.Plan{"grafana": planOf(catalogue.PlanProvision{Provision: "route", Provider: "ace", ProviderModule: "route-proxy"})},
"ace.grafana.route", "", "", "", provisions.Asked, "reads no plans of the seat"},
"ace.grafana.route", "", "", "", meshprovisionerclient.Asked, "reads no plans of the seat"},
} {
m := newMesh()
m.manifestOf("gitea", gitea)
@@ -588,7 +588,7 @@ func TestAKeyAheadOfItsWantCarriesItsPlanEntryAndSaysWhy(t *testing.T) {
if kc.Plan == nil || kc.Plan.Provision != "oidc-client" || kc.Plan.ProviderModule != "keycloak" || kc.Plan.As != "gitea-g14" {
t.Errorf("the key ahead of its want carries %+v", kc.Plan)
}
if kc.State != provisions.Asked || !strings.Contains(kc.Why, "no plan names it") {
if kc.State != meshprovisionerclient.Asked || !strings.Contains(kc.Why, "no plan names it") {
t.Errorf("the key ahead of its want: %s %q", kc.State, kc.Why)
}
// A key a plan names carries no entry: a reader on an older mesh-sdk, which refused a field it did not know, reads it
@@ -712,7 +712,7 @@ func TestARetirementIsJudgedOnlyFromAWholePicture(t *testing.T) {
}
retiring := 0
for i := range row.consumer {
if m.state(t, "novox."+apps[i]+".route.1").State == provisions.Retiring {
if m.state(t, "novox."+apps[i]+".route.1").State == meshprovisionerclient.Retiring {
retiring++
}
}
@@ -10,9 +10,9 @@ import (
"fmt"
"strings"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// The holder's tools.
@@ -133,7 +133,7 @@ func (h *Holder) read(state, key, node, want string) (any, error) {
}
row := map[string]any{"key": k}
if state == ProvisionsState {
p, err := provisions.Decode(e.Value)
p, err := meshprovisionerclient.Decode(e.Value)
if err != nil {
row["unreadable"] = err.Error()
} else {
+1
View File
@@ -3,6 +3,7 @@ module mesh-provisioner
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient v0.0.0-00010101000000-000000000000