Carry a channel's capabilities on its claim and tell the router every kind, so an answer is judged by the controller's record and not the channel's word

This commit is contained in:
jochen
2026-10-08 18:23:52 +02:00
parent 01b796f90d
commit eb62bb130a
8 changed files with 151 additions and 2 deletions
+43 -2
View File
@@ -84,6 +84,8 @@ type Placements struct {
// Interchangeable is each module whose definition says its instances are the same anywhere,
// so the module's plain subject is issued to all of them in one queue.
Interchangeable map[string]bool
// Kinds is every kind held of a kinded bench, by whom and with what capabilities (ADR 0259 §5).
Kinds []KindHeld
}
// AnswersForTheModule says whether an instance of a module on one machine is issued the module's
@@ -115,8 +117,18 @@ func MembershipFor(node string, d Declared, where Placements) Membership {
}
}
m.State = stateIssuedFor(d, node)
if t := SeatTrafficOf(d.Module, d.Holds, d.Uses, d.Watches); len(t.Publish)+len(t.Subscribe)+
len(t.Answers)+len(t.Workers)+len(t.Records) > 0 {
t := SeatTrafficOf(d.Module, d.Holds, d.Uses, d.Watches)
for _, s := range append(append([]Seat{}, d.Uses...), d.Watches...) {
if !s.Kinded {
continue
}
for _, k := range where.Kinds {
if k.Seat == s.Name && !kindListed(t.Kinds, k) {
t.Kinds = append(t.Kinds, k)
}
}
}
if len(t.Publish)+len(t.Subscribe)+len(t.Answers)+len(t.Workers)+len(t.Records)+len(t.Kinds) > 0 {
m.SeatTraffic = &t
}
if len(d.Invokes) > 0 {
@@ -155,5 +167,34 @@ func PlacementsOf(r Records, interchangeable map[string]bool) Placements {
for _, nodes := range p.Nodes {
sort.Strings(nodes)
}
for node, declared := range r.Assigned {
for _, d := range declared {
for _, s := range d.Holds {
if s.Kinded && s.Kind != "" {
p.Kinds = append(p.Kinds, KindHeld{Seat: s.Name, Kind: s.Kind, Module: d.Module, Node: node,
Capabilities: s.Capabilities})
}
}
}
}
sort.Slice(p.Kinds, func(i, j int) bool {
a, b := p.Kinds[i], p.Kinds[j]
if a.Seat != b.Seat {
return a.Seat < b.Seat
}
if a.Kind != b.Kind {
return a.Kind < b.Kind
}
return a.Node < b.Node
})
return p
}
func kindListed(list []KindHeld, k KindHeld) bool {
for _, x := range list {
if x.Seat == k.Seat && x.Kind == k.Kind && x.Module == k.Module && x.Node == k.Node {
return true
}
}
return false
}
+2
View File
@@ -75,6 +75,8 @@ type Seat struct {
Proofs []string
// Records are the holder's buckets, by their full name, each user reads under its own name.
Records []string
// Capabilities are what this principal's claim of a kinded bench promises (ADR 0234 §2).
Capabilities []string
}
// A Principal is one user of the bus. Its permissions are derived from what it declares and
+13
View File
@@ -47,6 +47,19 @@ type SeatTraffic struct {
Workers []Worker `json:"workers,omitempty"`
// Records is the direct-get subjects of the records it reads under its own name.
Records []string `json:"records,omitempty"`
// Kinds are the holders of every kinded bench it uses or watches, with what each promises: the one
// account of which channel is which, and what it can carry, that the router judges an answer by. The
// controller's, from the claims, never a channel's word (novox/hq ADR 0259 §5).
Kinds []KindHeld `json:"kinds,omitempty"`
}
// KindHeld is one kind of a kinded bench and who holds it.
type KindHeld struct {
Seat string `json:"seat"`
Kind string `json:"kind"`
Module string `json:"module"`
Node string `json:"node"`
Capabilities []string `json:"capabilities,omitempty"`
}
// KindedBenches are the seats that may be kinded (ADR 0234 §2): making another is a decision, recorded.
+32
View File
@@ -238,3 +238,35 @@ func TestASeatWithoutTheNewRulesIsComposedAsBefore(t *testing.T) {
t.Error("an old seat's holder lost its accept")
}
}
// The router learns which channel is which, and what each promises, from the controller's membership:
// the claims, never a channel's word (ADR 0259 §5).
func TestTheRoutersMembershipNamesEveryKindAndItsCapabilities(t *testing.T) {
tg := channelSeat("telegram")
tg.Capabilities = []string{"choice", "verified-sender"}
desk := channelSeat("desktop")
desk.Capabilities = []string{"choice"}
router := Declared{Module: "messenger", Holds: []Seat{operatorChannel()}, Uses: []Seat{channelSeat("")}}
records := Records{Nodes: []string{"anchor", "laptop"}, Assigned: map[string][]Declared{
"anchor": {router, {Module: "telegram", Holds: []Seat{tg}}},
"laptop": {{Module: "desk-channel", Holds: []Seat{desk}}},
}}
where := PlacementsOf(records, nil)
m := MembershipFor("anchor", router, where)
if m.SeatTraffic == nil || len(m.SeatTraffic.Kinds) != 2 {
t.Fatalf("the router is not told the kinds: %+v", m.SeatTraffic)
}
byKind := map[string]KindHeld{}
for _, k := range m.SeatTraffic.Kinds {
byKind[k.Kind] = k
}
if k := byKind["telegram"]; k.Module != "telegram" || k.Node != "anchor" || !namesVerb(k.Capabilities, "verified-sender") {
t.Errorf("telegram is %+v", k)
}
if k := byKind["desktop"]; k.Module != "desk-channel" || namesVerb(k.Capabilities, "verified-sender") {
t.Errorf("the desk is %+v", k)
}
if other := MembershipFor("anchor", Declared{Module: "mesh-delivery", Uses: []Seat{operatorChannel()}}, where); other.SeatTraffic != nil && len(other.SeatTraffic.Kinds) > 0 {
t.Error("an asker is told the channels")
}
}