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
+110 -3
View File
@@ -458,6 +458,8 @@ type consumers struct {
logf func(string, ...any)
mu sync.Mutex
of map[string]*moduleEvents
// seatBound is each module's seat traffic bound once: a worker taken, a proof subject answered.
seatBound map[string]func()
}
type moduleEvents struct {
@@ -475,6 +477,10 @@ func (c *consumers) stopAll() {
}
}
c.of = map[string]*moduleEvents{}
for _, stop := range c.seatBound {
stop()
}
c.seatBound = map[string]func(){}
}
// forModule is the bus one launched bundle of a module reaches the mesh through.
@@ -604,9 +610,10 @@ func eventRef(env bus.Envelope) string {
// stateAsked is what a bundle names when it reaches its state (ADR 0201): the state by the name its
// module uses, a key, and for a put the value.
type stateAsked struct {
State string `json:"state"`
Key string `json:"key"`
Value json.RawMessage `json:"value"`
State string `json:"state"`
Key string `json:"key"`
Value json.RawMessage `json:"value"`
Revision uint64 `json:"revision"`
}
// State answers a bundle's `get`, `put`, `delete` and `keys` on its module's state (ADR 0201). The
@@ -645,6 +652,20 @@ func (b *moduleBus) State(verb string, params json.RawMessage) (json.RawMessage,
return nil, err
}
answer = keys
case "create":
// Only when the key has no value: of two writers making it, one wins (novox/hq ADR 0259).
revision, err := conn.StateCreate(b.module, asked.State, asked.Key, asked.Value)
if err != nil {
return nil, err
}
answer = map[string]any{"revision": revision}
case "update":
// Only when the key is still at the revision read: a second answer to one ask loses (ADR 0259).
revision, err := conn.StateUpdate(b.module, asked.State, asked.Key, asked.Value, asked.Revision)
if err != nil {
return nil, err
}
answer = map[string]any{"revision": revision}
default:
return nil, fmt.Errorf("the runtime answers no mesh/state.%s", verb)
}
@@ -666,3 +687,89 @@ func (b *moduleBus) Watch(params json.RawMessage, deliver func(json.RawMessage)
return deliver(raw)
})
}
// seatAsked is what a bundle names for its seat traffic (novox/hq ADR 0259 §3).
type seatAsked struct {
Subject string `json:"subject"`
Body json.RawMessage `json:"body"`
ID string `json:"id"`
Worker string `json:"worker"`
Bucket string `json:"bucket"`
Key string `json:"key"`
}
// Seat answers a bundle's seat traffic, each as the module and only as far as its membership lists:
// `publish` an accept or an event, `prove` (ask a proof), `record` (read a record kept for it), `take` a
// worker's work and `answer` a proof subject — the last two bound once per module and subject, each
// message or proof handed to the module's child that asked last (novox/hq ADR 0259 §3).
func (b *moduleBus) Seat(verb string, params json.RawMessage, handOn func(string, any) (json.RawMessage, error)) (json.RawMessage, error) {
var asked seatAsked
if err := json.Unmarshal(params, &asked); err != nil {
return nil, fmt.Errorf("mesh/seat.%s: %w", verb, err)
}
conn := b.all.conn
switch verb {
case "publish":
seq, err := conn.SeatPublish(b.module, asked.Subject, asked.Body, asked.ID)
if err != nil {
return nil, err
}
return json.Marshal(map[string]any{"sequence": seq})
case "prove":
return conn.SeatProve(b.module, asked.Subject, asked.Body)
case "record":
entry, err := conn.SeatRecord(b.module, asked.Bucket, asked.Key)
if err != nil || entry == nil {
return json.RawMessage("null"), err
}
return json.Marshal(entry)
case "take":
key := "take " + asked.Worker
if b.bound(key) {
return json.RawMessage(`{}`), nil
}
stop, err := conn.SeatTake(b.module, asked.Worker, func(w bus.Work) error {
_, err := handOn("mesh/work", map[string]any{"work": w})
return err
})
if err != nil {
return nil, err
}
b.bind(key, stop)
b.all.logf("[mesh-tools] %s takes its work from %s", b.module, asked.Worker)
return json.RawMessage(`{}`), nil
case "answer":
key := "answer " + asked.Subject
if b.bound(key) {
return json.RawMessage(`{}`), nil
}
stop, err := conn.SeatAnswer(b.module, asked.Subject, func(subject string, body json.RawMessage) (json.RawMessage, error) {
return handOn("mesh/proof", map[string]any{"subject": subject, "body": body})
})
if err != nil {
return nil, err
}
b.bind(key, stop)
b.all.logf("[mesh-tools] %s answers the proofs on %s", b.module, asked.Subject)
return json.RawMessage(`{}`), nil
}
return nil, fmt.Errorf("the runtime answers no mesh/seat.%s", verb)
}
// bound and bind keep, per module, what its seat traffic is bound to, so a child that starts again and
// asks again is handed what is already bound instead of a second reader of it.
func (b *moduleBus) bound(key string) bool {
b.all.mu.Lock()
defer b.all.mu.Unlock()
_, ok := b.all.seatBound[b.module+" "+key]
return ok
}
func (b *moduleBus) bind(key string, stop func()) {
b.all.mu.Lock()
defer b.all.mu.Unlock()
if b.all.seatBound == nil {
b.all.seatBound = map[string]func(){}
}
b.all.seatBound[b.module+" "+key] = stop
}