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,107 @@
|
||||
package launch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A bundle that asks for one of its values and watches it, and writes each change it is handed to $OUT.
|
||||
const provisionBundle = `#!/bin/sh
|
||||
while IFS= read -r line; do
|
||||
id=$(printf '%s' "$line" | sed -n 's/.*"id":\([0-9]*\)[,}].*/\1/p')
|
||||
case "$line" in
|
||||
*'"method":"initialize"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{}}\n' "$id" ;;
|
||||
*'"method":"tools/list"'*)
|
||||
printf '{"jsonrpc":"2.0","id":%s,"result":{"tools":[]}}\n' "$id"
|
||||
printf '{"jsonrpc":"2.0","id":"g1","method":"mesh/provision.get","params":{"kind":"config","key":"role"}}\n'
|
||||
printf '{"jsonrpc":"2.0","id":"w1","method":"mesh/provision.watch","params":{"kind":"config","key":"role"}}\n' ;;
|
||||
*'"method":"mesh/provision"'*)
|
||||
printf '%s\n' "$line" >> "$OUT"
|
||||
printf '{"jsonrpc":"2.0","id":%s,"result":{}}\n' "$id" ;;
|
||||
*'"id":"g1"'*) printf '%s\n' "$line" >> "$OUT" ;;
|
||||
esac
|
||||
done
|
||||
`
|
||||
|
||||
type provisioning struct {
|
||||
subscribing
|
||||
mu sync.Mutex
|
||||
asked []string
|
||||
}
|
||||
|
||||
func (b *provisioning) Provision(verb string, params json.RawMessage) (json.RawMessage, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.asked = append(b.asked, verb+" "+string(params))
|
||||
return json.RawMessage(`"the build machine"`), nil
|
||||
}
|
||||
|
||||
func (b *provisioning) ProvisionWatch(params json.RawMessage, deliver func(json.RawMessage) error) (func(), error) {
|
||||
b.mu.Lock()
|
||||
b.asked = append(b.asked, "watch "+string(params))
|
||||
b.mu.Unlock()
|
||||
return func() {}, deliver(json.RawMessage(`{"kind":"config","key":"role","value":"the build machine"}`))
|
||||
}
|
||||
|
||||
// novox/hq ADR 0349 §14, ADR 0370 §4: a bundle's ask for its values reaches the runtime's bus from its own channel,
|
||||
// answered there, and a watch's change is handed to it naming the watch it asked.
|
||||
func TestABundleAsksForItsValuesAndIsHandedItsWatchsChanges(t *testing.T) {
|
||||
entry := filepath.Join(t.TempDir(), "bundle")
|
||||
if err := os.WriteFile(entry, []byte(provisionBundle), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := filepath.Join(t.TempDir(), "out")
|
||||
b := &provisioning{}
|
||||
l, err := Start("claude-code", entry, append(os.Environ(), "OUT="+out), b, t.Logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(l.Stop)
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
for {
|
||||
said, _ := os.ReadFile(out)
|
||||
if strings.Count(string(said), "\n") >= 2 {
|
||||
// Each line as the bundle read it, by what it is: the get's answer, and the watch's change.
|
||||
got := map[string]string{}
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(said)), "\n") {
|
||||
var m struct {
|
||||
ID any `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(line), &m); err != nil {
|
||||
t.Fatalf("the bundle read %s", line)
|
||||
}
|
||||
if m.Method != "" {
|
||||
got[m.Method] = string(m.Params)
|
||||
} else {
|
||||
got["answer"] = string(m.Result)
|
||||
}
|
||||
}
|
||||
if got["answer"] != `"the build machine"` {
|
||||
t.Errorf("the get was answered %s", got["answer"])
|
||||
}
|
||||
if got["mesh/provision"] != `{"change":{"kind":"config","key":"role","value":"the build machine"},"watch":"w1"}` {
|
||||
t.Errorf("the watch was handed %s", got["mesh/provision"])
|
||||
}
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("the bundle was handed %q", said)
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
slices.Sort(b.asked)
|
||||
if len(b.asked) != 2 || b.asked[0] != `get {"kind":"config","key":"role"}` || b.asked[1] != `watch {"kind":"config","key":"role"}` {
|
||||
t.Errorf("the runtime's bus was asked %q", b.asked)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user