Let node-tools' provider helper read mesh-provisioner's provisions and plans by mesh-sdk's names, so the runtime follows ADR 0570's records off the controller (issue 1954 L6-T1)

Pins mesh-sdk 4a96793 (go/provisions). The assignments read stays while the
sdk's helper still reads plans there (until L6-P3). A watch's expire now
reads as a removed key.
This commit is contained in:
2026-10-29 22:34:34 +01:00
parent 1583fa85d9
commit 9fc97e45c9
5 changed files with 38 additions and 4 deletions
+10 -2
View File
@@ -16,6 +16,7 @@ import (
"time"
"git.novox.be/novox/mesh-sdk/go/provider"
"git.novox.be/novox/mesh-sdk/go/provisions"
"github.com/novox/mesh-tools/node-tools/internal/bus"
)
@@ -38,13 +39,19 @@ func Offers() map[string]provider.Offer {
}
// busOf is the helper's bus over the runtime's own state: the provider's bucket is its state `bound`, and the
// controller's assignments are the state it reads, `mesh-controller.assignments` (both declared in the manifest).
// provisions and plans of the seat mesh-provisioner are the states it reads, by mesh-sdk's names (novox/hq ADR 0570,
// issue 1954 L6-T1). The controller's assignments stay readable while the sdk's helper still reads its plans there
// (until L6-P3 moves it); the constant's removal from the sdk then fails this build, so the read goes with it.
type busOf struct{ conn *bus.Conn }
func (b busOf) state(bucket string) (string, error) {
switch bucket {
case provider.BucketOf(Module):
return "bound", nil
case provisions.ProvisionsBucket:
return provisions.ProvisionsRead, nil
case provisions.PlansBucket:
return provisions.PlansRead, nil
case provider.AssignmentsBucket:
return "mesh-controller.assignments", nil
}
@@ -57,7 +64,8 @@ func (b busOf) Watch(ctx context.Context, bucket string, handle func(key string,
return err
}
stop, err := b.conn.StateWatch(Module, name, "", func(ch bus.StateChange) error {
if ch.Op == "delete" {
if ch.Op != "put" {
// A delete, or an expiry (issue 1952): the key is gone.
handle(ch.Key, nil)
} else {
handle(ch.Key, ch.Value)
@@ -7,6 +7,9 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/provider"
"git.novox.be/novox/mesh-sdk/go/provisions"
"github.com/novox/mesh-tools/node-tools/internal/bus"
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
)
@@ -102,6 +105,8 @@ func TestTheOfferServesThePortTheManifestServes(t *testing.T) {
var m struct {
Serves map[string]map[string]any `json:"serves"`
Moved bool `json:"moved"`
State []string `json:"state"`
Reads []string `json:"reads"`
Provides []struct {
Name string `json:"name"`
AnsweredBy string `json:"answered-by"`
@@ -138,6 +143,23 @@ func TestTheOfferServesThePortTheManifestServes(t *testing.T) {
t.Errorf("the build still carries %+v, which only a moved module needs", a)
}
}
// Every bucket the sdk's helper reaches is a state the manifest keeps or reads, by the sdk's names: the provider's
// own, the seat mesh-provisioner's provisions and plans (issue 1954 L6-T1), and the assignments the helper still reads.
declared := map[string]bool{}
for _, n := range append(m.State, m.Reads...) {
declared[n] = true
}
for bucket, want := range map[string]string{
provider.BucketOf(Module): "bound",
provisions.ProvisionsBucket: "mesh-provisioner.provisions",
provisions.PlansBucket: "mesh-provisioner.plans",
provider.AssignmentsBucket: "mesh-controller.assignments",
} {
got, err := (busOf{}).state(bucket)
if err != nil || got != want || !declared[got] {
t.Errorf("the helper's bucket %s reaches state %q (%v), want %q declared in the manifest", bucket, got, err, want)
}
}
}
// Only the machine's runtime is the provider: a process on another credential, or with no machine, answers nobody.