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:
@@ -84,6 +84,8 @@ type Placements struct {
|
|||||||
// Interchangeable is each module whose definition says its instances are the same anywhere,
|
// 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.
|
// so the module's plain subject is issued to all of them in one queue.
|
||||||
Interchangeable map[string]bool
|
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
|
// 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)
|
m.State = stateIssuedFor(d, node)
|
||||||
if t := SeatTrafficOf(d.Module, d.Holds, d.Uses, d.Watches); len(t.Publish)+len(t.Subscribe)+
|
t := SeatTrafficOf(d.Module, d.Holds, d.Uses, d.Watches)
|
||||||
len(t.Answers)+len(t.Workers)+len(t.Records) > 0 {
|
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
|
m.SeatTraffic = &t
|
||||||
}
|
}
|
||||||
if len(d.Invokes) > 0 {
|
if len(d.Invokes) > 0 {
|
||||||
@@ -155,5 +167,34 @@ func PlacementsOf(r Records, interchangeable map[string]bool) Placements {
|
|||||||
for _, nodes := range p.Nodes {
|
for _, nodes := range p.Nodes {
|
||||||
sort.Strings(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
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ type Seat struct {
|
|||||||
Proofs []string
|
Proofs []string
|
||||||
// Records are the holder's buckets, by their full name, each user reads under its own name.
|
// Records are the holder's buckets, by their full name, each user reads under its own name.
|
||||||
Records []string
|
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
|
// A Principal is one user of the bus. Its permissions are derived from what it declares and
|
||||||
|
|||||||
@@ -47,6 +47,19 @@ type SeatTraffic struct {
|
|||||||
Workers []Worker `json:"workers,omitempty"`
|
Workers []Worker `json:"workers,omitempty"`
|
||||||
// Records is the direct-get subjects of the records it reads under its own name.
|
// Records is the direct-get subjects of the records it reads under its own name.
|
||||||
Records []string `json:"records,omitempty"`
|
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.
|
// KindedBenches are the seats that may be kinded (ADR 0234 §2): making another is a decision, recorded.
|
||||||
|
|||||||
@@ -238,3 +238,35 @@ func TestASeatWithoutTheNewRulesIsComposedAsBefore(t *testing.T) {
|
|||||||
t.Error("an old seat's holder lost its accept")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,3 +93,24 @@ func TestEachKindIsItsOwnHolderWhenResolved(t *testing.T) {
|
|||||||
t.Fatal("one kind held on two machines was not refused")
|
t.Fatal("one kind held on two machines was not refused")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A channel's capabilities come from the fixed vocabulary, and only a kinded claim carries any.
|
||||||
|
func TestCapabilitiesAreTheVocabularysAndOnlyOnAKindedClaim(t *testing.T) {
|
||||||
|
good := aChannel("telegram", "telegram")
|
||||||
|
good.Claims[0].Capabilities = []string{"deliver", "choice", "verified-sender", "max-length:4096"}
|
||||||
|
if got := problemsFor(t, Shelf{"messenger": router(), "telegram": good}); got != "" {
|
||||||
|
t.Fatalf("the vocabulary was refused: %s", got)
|
||||||
|
}
|
||||||
|
bad := aChannel("telegram", "telegram")
|
||||||
|
bad.Claims[0].Capabilities = []string{"trusted", "max-length:lots"}
|
||||||
|
got := problemsFor(t, Shelf{"messenger": router(), "telegram": bad})
|
||||||
|
for _, w := range []string{`"trusted"`, `"max-length:lots"`} {
|
||||||
|
if !strings.Contains(got, w+", which channel-capabilities/1 does not have") {
|
||||||
|
t.Errorf("%s was not refused: %s", w, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
odd := Manifest{Module: "odd", Claims: []Claim{{Name: "operator-channel", Scope: ScopeMesh, Capabilities: []string{"deliver"}}}}
|
||||||
|
if got := problemsFor(t, Shelf{"messenger": router(), "odd": odd}); !strings.Contains(got, "only a kinded bench's claim carries them") {
|
||||||
|
t.Errorf("capabilities on a seat that is not kinded stood: %s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ type Claim struct {
|
|||||||
// Kind is the kind this module holds a kinded bench as (novox/hq ADR 0234 §2, ADR 0259): `telegram`,
|
// Kind is the kind this module holds a kinded bench as (novox/hq ADR 0234 §2, ADR 0259): `telegram`,
|
||||||
// `desktop`. Refused on any other seat, and a second claim of one kind is refused.
|
// `desktop`. Refused on any other seat, and a second claim of one kind is refused.
|
||||||
Kind string `json:"kind,omitempty"`
|
Kind string `json:"kind,omitempty"`
|
||||||
|
// Capabilities are what a channel of this kind promises, from the fixed vocabulary
|
||||||
|
// channel-capabilities/1 (novox/hq ADR 0234 §2): the router judges an answer by these, read from the
|
||||||
|
// controller's record of this claim and never from the channel.
|
||||||
|
Capabilities []string `json:"capabilities,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServesFor is what this claim offers a seat's protocol: the verbs it names, else the module's
|
// ServesFor is what this claim offers a seat's protocol: the verbs it names, else the module's
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package catalogue
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -331,9 +332,43 @@ func CatalogueProblems(shelf Shelf) []string {
|
|||||||
return problems
|
return problems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChannelCapabilities is the fixed vocabulary `channel-capabilities/1` (novox/hq ADR 0234 §2): a word
|
||||||
|
// outside it is refused. `max-length:<N>` takes a number.
|
||||||
|
var ChannelCapabilities = map[string]bool{
|
||||||
|
"deliver": true, "reaches-away": true, "loud": true, "silent": true, "edit": true,
|
||||||
|
"reaches-when-mesh-down": true, "private": true,
|
||||||
|
"choice": true, "reply": true, "threads": true, "operator-first": true,
|
||||||
|
"verified-sender": true, "exact-render": true, "code-factor": true, "key-factor": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxLength = regexp.MustCompile(`^max-length:[1-9][0-9]{0,6}$`)
|
||||||
|
|
||||||
|
// capabilityProblems are the words of a claim outside the vocabulary, and capabilities on a claim of a
|
||||||
|
// seat that is not kinded.
|
||||||
|
func capabilityProblems(module string, c Claim, kinded bool) []string {
|
||||||
|
if len(c.Capabilities) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !kinded {
|
||||||
|
return []string{fmt.Sprintf("%s claims %s with capabilities, and only a kinded bench's claim carries them",
|
||||||
|
module, c.Name)}
|
||||||
|
}
|
||||||
|
var problems []string
|
||||||
|
for _, w := range c.Capabilities {
|
||||||
|
if !ChannelCapabilities[w] && !maxLength.MatchString(w) {
|
||||||
|
problems = append(problems, fmt.Sprintf(
|
||||||
|
"%s claims %s with the capability %q, which channel-capabilities/1 does not have", module, c.Name, w))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems
|
||||||
|
}
|
||||||
|
|
||||||
// kindProblems is a claim judged against a declared seat's kind: a kinded bench takes one claim per kind,
|
// kindProblems is a claim judged against a declared seat's kind: a kinded bench takes one claim per kind,
|
||||||
// a usable name; any other seat takes none.
|
// a usable name; any other seat takes none.
|
||||||
func kindProblems(module string, c Claim, s SeatDeclaration, taken map[string]string) []string {
|
func kindProblems(module string, c Claim, s SeatDeclaration, taken map[string]string) []string {
|
||||||
|
if problems := capabilityProblems(module, c, s.Kinded); len(problems) > 0 {
|
||||||
|
return problems
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case !s.Kinded && c.Kind != "":
|
case !s.Kinded && c.Kind != "":
|
||||||
return []string{fmt.Sprintf("%s claims %s of kind %q, and only a kinded bench takes a kind",
|
return []string{fmt.Sprintf("%s claims %s of kind %q, and only a kinded bench takes a kind",
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio
|
|||||||
if s, hasAProtocol := seats[c.Name]; hasAProtocol {
|
if s, hasAProtocol := seats[c.Name]; hasAProtocol {
|
||||||
held := asSeat(s, declarers[s.Name])
|
held := asSeat(s, declarers[s.Name])
|
||||||
held.Kind = c.Kind
|
held.Kind = c.Kind
|
||||||
|
held.Capabilities = c.Capabilities
|
||||||
d.Holds = append(d.Holds, held)
|
d.Holds = append(d.Holds, held)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user