Carry a bundle's seat traffic under its own name and kind, and start a module as its own account (hq ADR 0259)

This commit is contained in:
jochen
2026-10-09 10:12:39 +02:00
committed by jschoubben
parent 66de6774e0
commit 6d9906b6c9
9 changed files with 922 additions and 4 deletions
+39 -1
View File
@@ -60,6 +60,10 @@ type Bus interface {
Subscribe(deliver func(envelope json.RawMessage) error) error
State(verb string, params json.RawMessage) (json.RawMessage, error)
Watch(params json.RawMessage, deliver func(change json.RawMessage) error) (stop func(), err error)
// Seat answers a bundle's seat traffic (novox/hq ADR 0259 §3): `publish`, `prove` and `record` at once;
// `take` and `answer` bind once and hand each message or proof on through handOn, to whichever child
// of the module asked last — a restarted child asks again as its code runs again.
Seat(verb string, params json.RawMessage, handOn func(method string, params any) (json.RawMessage, error)) (json.RawMessage, error)
}
// EventTimeout bounds how long a bundle has to handle one event before it is offered again.
@@ -147,6 +151,17 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
// The child that last subscribed is the one events are handed to: a restarted child subscribes
// again as its code is imported, and from then on the module's events are its.
var subscriber *child
// The child that last asked to take a seat's work or answer its proofs is the one they are handed to.
var seatTaker *child
handOn := func(method string, params any) (json.RawMessage, error) {
mu.Lock()
c := seatTaker
mu.Unlock()
if c == nil {
return nil, errors.New(module + "'s bundle is not running to take it")
}
return c.ask(module, method, params, EventTimeout)
}
stopped := false
deliver := func(envelope json.RawMessage) error {
mu.Lock()
@@ -166,6 +181,12 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
start := func() (*child, error) {
cmd := exec.Command(entry)
cmd.Env = env
// **A module that names an account of its own runs as it** (novox/hq ADR 0259 §8): its secrets and
// state are that account's, and the operator's account — which every agent runs as — reaches
// neither. Never root, never the operator's.
if err := runAs(cmd, env); err != nil {
return nil, err
}
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err
@@ -237,7 +258,21 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
subscriber = c
mu.Unlock()
err = mesh.Subscribe(deliver)
case "mesh/state.get", "mesh/state.put", "mesh/state.delete", "mesh/state.keys":
case "mesh/seat.publish", "mesh/seat.prove", "mesh/seat.record":
var answered json.RawMessage
if answered, err = mesh.Seat(strings.TrimPrefix(m.Method, "mesh/seat."), m.Params, nil); err == nil {
result = answered
}
case "mesh/seat.take", "mesh/seat.answer":
mu.Lock()
seatTaker = c
mu.Unlock()
var answered json.RawMessage
if answered, err = mesh.Seat(strings.TrimPrefix(m.Method, "mesh/seat."), m.Params, handOn); err == nil {
result = answered
}
case "mesh/state.get", "mesh/state.put", "mesh/state.delete", "mesh/state.keys",
"mesh/state.create", "mesh/state.update":
var answered json.RawMessage
if answered, err = mesh.State(strings.TrimPrefix(m.Method, "mesh/state."), m.Params); err == nil {
result = answered
@@ -329,6 +364,9 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
if subscriber == c {
subscriber = nil
}
if seatTaker == c {
seatTaker = nil
}
wasStopped := stopped
// Started again at once if it ran a while; after a growing pause while it keeps exiting.
wait := RestartFirst