Files
mesh-sdk/go/provision.go
T
mesh-tools 1bc5719b8b
mesh/merge-gate head 1bc5719b8b85 merged into trunk a2d2af00dc5d beside mesh-host#116@bff1ac14 — pass, with a note: builds mesh-sdk (+40 dependent(s)) → ac…
mesh/repo-check head 1bc5719b8b85 merged into trunk a2d2af00dc5d beside mesh-host#116@bff1ac14 — pass: its merge-check.sh passed, each part in its toolchai…
mesh/delivery delivered
mesh/delivery-group group release/57-config-and-provisioning delivered: every member is delivered
Feature 57 (config and provisioning): release mesh-sdk, the reviewed release branch on top of main
2026-10-16 08:24:11 +00:00

91 lines
3.5 KiB
Go

package stdio
import (
"encoding/json"
"errors"
"time"
)
// A module's code reads its values at runtime (novox/hq ADR 0349 clause 14, ADR 0370, design 57 S1): its config
// values and its bindings' values that are not secret, for what its manifest declares, and watches them. The
// runtime that launched this bundle asks its machine's provisioner client **as this bundle's module** — the module it
// launched, never a name this code passes — and the client answers only what that module declares, only while the
// mesh places it on that machine. A secret is refused while the module's code runs in the tool runner: the vault
// hands a secret only to a caller the bus proves, and the bus proves only the tool runner there, so a module's
// secrets reach it through its files until it runs under a bus account of its own (novox/hq issue 569).
// ProvisionChange is what a watch hands over: the value now, or — once it is no longer answered, the module taken
// off the machine or the key no longer declared — Refused, why.
type ProvisionChange struct {
Kind string `json:"kind"`
Key string `json:"key"`
Value json.RawMessage `json:"value,omitempty"`
Refused string `json:"refused,omitempty"`
}
const provisionTimeout = 30 * time.Second
func provisionGet(kind, key string) (json.RawMessage, error) {
c, err := live()
if err != nil {
return nil, err
}
return c.ask("mesh/provision.get", map[string]any{"kind": kind, "key": key}, provisionTimeout)
}
func provisionWatch(kind, key string, handle func(ProvisionChange) error) error {
if handle == nil {
return errors.New("a watch needs a handler")
}
c, err := live()
if err != nil {
return err
}
id := c.newID()
c.mu.Lock()
c.provisioned[id] = handle
c.mu.Unlock()
if _, err := c.askAs(id, "mesh/provision.watch", map[string]any{"kind": kind, "key": key}, provisionTimeout); err != nil {
c.mu.Lock()
delete(c.provisioned, id)
c.mu.Unlock()
return err
}
return nil
}
// Config is one of this module's config values as mesh-config resolved it for this machine, as JSON: the key as
// its manifest declares it under `config`. A key it does not declare is refused, with the reason.
func Config(key string) (json.RawMessage, error) { return provisionGet("config", key) }
// WatchConfig hands handle the config value now and then every change, in order, and returns once the value now is
// handled. The watch lasts as long as the process.
func WatchConfig(key string, handle func(ProvisionChange) error) error {
return provisionWatch("config", key, handle)
}
// Bound is one value, not a secret, of what this module is bound to: the provision as its manifest requires it,
// and the key its provider answers (`${bound:<provision>:<key>}`).
func Bound(provision, key string) (json.RawMessage, error) {
return provisionGet("bound", provision+":"+key)
}
// WatchBound is WatchConfig for a bound value.
func WatchBound(provision, key string, handle func(ProvisionChange) error) error {
return provisionWatch("bound", provision+":"+key, handle)
}
// Secret is one of this module's secrets. Refused, with the reason, while the module's code runs in the tool
// runner (novox/hq ADR 0349 §11, ADR 0370 §2): its secrets reach it through its files.
func Secret(name string) ([]byte, error) {
raw, err := provisionGet("secret", name)
if err != nil {
return nil, err
}
var s string
if err := json.Unmarshal(raw, &s); err != nil {
return nil, errors.New("the runtime answered a secret that is not a string")
}
return []byte(s), nil
}