Feature 57 (config and provisioning): release mesh-tools (the provider's StateBus, the SDK pin, templates), reviewed
mesh/merge-gate head 33d136e70227 merged into trunk 6b3e15042fee — pass, with a note: builds mesh-tools, node-tools (+40 dependent(s)) → ace, g14, novox, s…
mesh/repo-check head 33d136e70227 merged into trunk 6b3e15042fee — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the ch…
mesh/delivery delivered
mesh/merge-gate head 33d136e70227 merged into trunk 6b3e15042fee — pass, with a note: builds mesh-tools, node-tools (+40 dependent(s)) → ace, g14, novox, s…
mesh/repo-check head 33d136e70227 merged into trunk 6b3e15042fee — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the ch…
mesh/delivery delivered
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
// Package provide is the runtime's half of node-tools being a provider of `mcp-endpoint`: it runs the mesh SDK's
|
||||
// provider helper, so a consumer handed to the provisioner (claude-code's `${bound:mcp-endpoint:port}`) is answered
|
||||
// from an entry node-tools writes in its own bucket, not from the controller's binding file (novox/hq ADR 0349
|
||||
// clause 12, issue 791).
|
||||
//
|
||||
// The offer carries no credential: the console's authority is the machine's login (ADR 0152, 0175), so the helper
|
||||
// reads nothing from the vault, needs no consumers' file and the entry names none. node-tools is not marked moved:
|
||||
// its manifest says `"answered-by": "helper"` for the provision, and the helper runs whenever the runtime does
|
||||
// (issue 791), as route-proxy's does (issue 792).
|
||||
package provide
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/provider"
|
||||
|
||||
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
||||
)
|
||||
|
||||
const (
|
||||
// Module is the provider: the runtime's own module.
|
||||
Module = "node-tools"
|
||||
// Provision is what node-tools provides, as its manifest says.
|
||||
Provision = "mcp-endpoint"
|
||||
// ServedPort is the port its manifest serves (`serves.mcp-endpoint.port`): where the console listens.
|
||||
ServedPort = 4270
|
||||
)
|
||||
|
||||
// Offers is what node-tools serves, per provision.
|
||||
func Offers() map[string]provider.Offer {
|
||||
return map[string]provider.Offer{Provision: {
|
||||
Credential: provider.NoCredential,
|
||||
Serves: map[string]any{"port": ServedPort},
|
||||
}}
|
||||
}
|
||||
|
||||
// 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).
|
||||
type busOf struct{ conn *bus.Conn }
|
||||
|
||||
func (b busOf) state(bucket string) (string, error) {
|
||||
switch bucket {
|
||||
case provider.BucketOf(Module):
|
||||
return "bound", nil
|
||||
case provider.AssignmentsBucket:
|
||||
return "mesh-controller.assignments", nil
|
||||
}
|
||||
return "", fmt.Errorf("%s is not a bucket this provider keeps or reads", bucket)
|
||||
}
|
||||
|
||||
func (b busOf) Watch(ctx context.Context, bucket string, handle func(key string, value []byte)) error {
|
||||
name, err := b.state(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stop, err := b.conn.StateWatch(Module, name, "", func(ch bus.StateChange) error {
|
||||
if ch.Op == "delete" {
|
||||
handle(ch.Key, nil)
|
||||
} else {
|
||||
handle(ch.Key, ch.Value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() { <-ctx.Done(); stop() }()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b busOf) Put(_ context.Context, bucket, key string, value []byte) error {
|
||||
name, err := b.state(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = b.conn.StatePut(Module, name, key, json.RawMessage(value))
|
||||
return err
|
||||
}
|
||||
|
||||
func (b busOf) Delete(_ context.Context, bucket, key string) error {
|
||||
name, err := b.state(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.conn.StateDelete(Module, name, key)
|
||||
}
|
||||
|
||||
// New is the helper answering node-tools's consumers on node.
|
||||
func New(conn *bus.Conn, node string, every time.Duration, logf func(string, ...any)) *provider.Provider {
|
||||
return provider.New(provider.Options{
|
||||
Node: node,
|
||||
Module: Module,
|
||||
Offers: Offers(),
|
||||
Bus: busOf{conn: conn},
|
||||
Every: every,
|
||||
Logf: logf,
|
||||
})
|
||||
}
|
||||
|
||||
// Run answers the consumers until ctx ends. The state is not reachable until the mesh has issued the runtime its
|
||||
// membership and made the buckets, so a start that cannot watch is tried again, not given up.
|
||||
func Run(ctx context.Context, conn *bus.Conn, node string, every time.Duration, logf func(string, ...any)) {
|
||||
for delay := 2 * time.Second; ctx.Err() == nil; delay = min(delay*2, time.Minute) {
|
||||
attempt, cancel := context.WithCancel(ctx)
|
||||
err := New(conn, node, every, logf).Run(attempt)
|
||||
cancel()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
logf("[mesh-tools] the provider helper stopped: %v; trying again in %s", err, delay)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start runs the helper when this process is the machine's runtime (the node-tools credential, on a machine's
|
||||
// name), with no condition on the environment, and returns what ends it. A runtime on another credential, a module's
|
||||
// own in a test, is not the provider and answers nobody.
|
||||
func Start(conn *bus.Conn, module, node string, every time.Duration, logf func(string, ...any)) (stop func()) {
|
||||
if module != Module || node == "" {
|
||||
return func() {}
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
logf("[mesh-tools] answering %s's consumers of %s through the provider helper", Module, Provision)
|
||||
go Run(ctx, conn, node, every, logf)
|
||||
return cancel
|
||||
}
|
||||
Reference in New Issue
Block a user