serve8: a process's bus declares the seat verbs it serves

Builds: design 47 G A-107, A-109, A-110
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-11 23:58:13 +01:00
parent ba6c2a5207
commit db73c7a629
2 changed files with 27 additions and 5 deletions
+9 -5
View File
@@ -13,8 +13,8 @@ const ProcessBusField = "bus"
// ProcessBus is what one process of a module does on the bus (novox/hq ADR 0436 §1, §2, §3, §5): its own bus
// account, granted exactly this and nothing of the module's other grants or the tool runner's — never a seat
// to hold or serve, never a tool call or seat verb answered, never a restricted stream heard, never a
// warrant published (§3, §5).
// to hold, never a restricted stream heard, never a warrant published (§3, §5). It answers a seat's verb only
// where it declares it in Serves (design 47 G A-107, A-109).
type ProcessBus struct {
// Emits are the events this process emits, named locally, as the module's own Emits names them
// (EmitsAll): a process emits only what its module may emit as a whole.
@@ -28,6 +28,10 @@ type ProcessBus struct {
// Invokes are the tools and seat verbs this process calls, each <module>.<tool> or a role's
// seat:<seat>.<verb> (as Manifest.Invokes), or * for every tool on the mesh.
Invokes []string `json:"invokes,omitempty"`
// Serves are the seat verbs this process answers directly over the bus (design 47 G A-107, A-109, A-110), each
// seat:<seat>.<verb>: the grants part composes the process account's subscribe of the verb's call and tool
// subjects from it, and, for a verb whose seat definition says chunks: true, the chunk and ack subjects.
Serves []string `json:"serves,omitempty"`
// Removes are what a tools bundle's account may remove from the bus, from a closed set (BusRemovals; novox/hq ADR
// 0564 Limits 4): declared alone, by the module holding mesh-broker, never by a process.
Removes []string `json:"removes,omitempty"`
@@ -88,8 +92,8 @@ func (m Manifest) BusAccounts() []BusAccount {
return out
}
// UnmarshalJSON reads a process's bus strictly: an unknown key — serves, a seat to hold or claim, or
// anything else that would answer a tool call or a seat's verb — is refused here, by name (novox/hq ADR
// UnmarshalJSON reads a process's bus strictly: an unknown key — a seat to hold or claim, or
// anything else that would answer a tool call — is refused here, by name (novox/hq ADR
// 0436 §3).
func (b *ProcessBus) UnmarshalJSON(raw []byte) error {
type plain ProcessBus
@@ -97,7 +101,7 @@ func (b *ProcessBus) UnmarshalJSON(raw []byte) error {
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&p); err != nil {
return fmt.Errorf("a process's bus is {emits, queues, state, asks, invokes, keeps}: %w", typedUnknown(err))
return fmt.Errorf("a process's bus is {emits, queues, state, asks, invokes, serves, keeps}: %w", typedUnknown(err))
}
*b = ProcessBus(p)
return nil
+18
View File
@@ -1,5 +1,10 @@
package catalogue
import (
"encoding/json"
"testing"
)
func strSliceEqual(a, b []string) bool {
if len(a) != len(b) {
return false
@@ -11,3 +16,16 @@ func strSliceEqual(a, b []string) bool {
}
return true
}
func TestProcessBusReadsServesAndRefusesUnknownKeys(t *testing.T) {
var b ProcessBus
if err := json.Unmarshal([]byte(`{"serves":["seat:mesh-broker.snapshot-get","seat:mesh-broker.list"]}`), &b); err != nil {
t.Fatal(err)
}
if !strSliceEqual(b.Serves, []string{"seat:mesh-broker.snapshot-get", "seat:mesh-broker.list"}) {
t.Errorf("serves = %v", b.Serves)
}
if err := json.Unmarshal([]byte(`{"holds":["x"]}`), &ProcessBus{}); err == nil {
t.Error("an unknown key was accepted")
}
}