Item 5, area 8 (join 11): the grants part lets a caller of a marked verb hear its own calls' ends
A holder says a held call's end on its seat's gated-ended, kept last per subject for a day in SEAT_<seat>_gated-ended, which the grants part makes from the definition seatdef.Resolve gives (A-264). The caller is granted hearing gated-ended.<caller>.> under its own escaped account name, and an ordered consumer on that stream filtered to it, from its invokes of the marked verb by name; a module or process that answers a marked verb is granted saying the event to any caller. No process of mesh-delivery holds a marked verb, so no holder here takes NewGateChannel. Builds: design 47 G §677, §700, A-264, A-273 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package buscompose
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
)
|
||||
|
||||
// **A held call's end is heard by its caller alone** (design 47 G §677; lead answer A-264): a holder says the end of a call of
|
||||
// a marked verb on its seat's event `gated-ended`, subject `mesh.seat.<seat>.event.gated-ended.<caller>.<call>`, `<caller>` the
|
||||
// account the bus proved escaped as a key's name is, kept last per subject for a day in the stream SEAT_<seat>_gated-ended
|
||||
// (KeptEventsOf, from the definition seatdef.Resolve gives). The caller is granted hearing `gated-ended.<caller>.>` and no other
|
||||
// caller's, from its invokes of the marked verb by name; it reads the stream through an ordered consumer filtered to its own
|
||||
// subjects (mesh-sdk process awaitGated), made for the read. A process that answers a marked verb says the event to any caller.
|
||||
|
||||
// gatedEndedGrants are the grants of the principal user from the marked verbs (`seat:<seat>.<verb>`) it invokes by name and
|
||||
// those it answers.
|
||||
func gatedEndedGrants(user string, marked, invokes, answers []string) (pub, sub []string) {
|
||||
caller := keys.EscapeName(user)
|
||||
if user == "" {
|
||||
return nil, nil
|
||||
}
|
||||
var heard, said []string
|
||||
for _, v := range marked {
|
||||
seat, _, ok := strings.Cut(strings.TrimPrefix(v, "seat:"), ".")
|
||||
if !ok || !safeSubject.MatchString(seat) {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(invokes, v) {
|
||||
heard = append(heard, seat)
|
||||
}
|
||||
if slices.Contains(answers, v) {
|
||||
said = append(said, seat)
|
||||
}
|
||||
}
|
||||
for _, seat := range unique(heard) {
|
||||
stream := seatdef.KeptStream(seat, seatdef.Message{Name: seatdef.GatedEndedEvent})
|
||||
own := GatedEndedSubject(seat, caller) + ".>"
|
||||
sub = append(sub, own)
|
||||
pub = append(pub, "$JS.API.STREAM.INFO."+stream, "$JS.API.CONSUMER.CREATE."+stream+".*."+own,
|
||||
"$JS.API.CONSUMER.MSG.NEXT."+stream+".*", "$JS.API.CONSUMER.INFO."+stream+".*", "$JS.API.CONSUMER.DELETE."+stream+".*")
|
||||
}
|
||||
for _, seat := range unique(said) {
|
||||
pub = append(pub, GatedEndedSubject(seat, "*")+".*")
|
||||
}
|
||||
return pub, sub
|
||||
}
|
||||
|
||||
// GatedEndedSubject is the subject of the seat's gated-ended for one caller, its escaped name (keys.EscapeName) or `*`.
|
||||
func GatedEndedSubject(seat, caller string) string {
|
||||
return seatdef.EventSubject(seat, seatdef.GatedEndedEvent) + "." + caller
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package buscompose
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
)
|
||||
|
||||
// A held call's end is heard by its caller alone (design 47 G §677, A-264): the definition seatdef.Resolve gives a marked verb
|
||||
// keeps gated-ended in SEAT_<seat>_gated-ended, last per subject for a day; a module's and a process's account that invoke the
|
||||
// marked verb by name hear `gated-ended.<caller>.>` under their own escaped name and read the stream filtered to it, one that
|
||||
// does not invoke it hears nothing, and a process that answers it says the event to any caller.
|
||||
func TestAHeldCallsEndIsHeardByItsCallerAlone(t *testing.T) {
|
||||
def, err := seatdef.Resolve(seatdef.Definition{Seat: "node-login-shell", Verbs: []seatdef.Verb{{Name: "execute", NeedsApproval: true}}}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
kept := KeptEventsOf(map[string]seatdef.Definition{def.Seat: def})
|
||||
if len(kept) != 1 || kept[0].Stream != "SEAT_node-login-shell_gated-ended" || !kept[0].Last || kept[0].MaxAgeDays != 1 ||
|
||||
kept[0].Filter != "mesh.seat.node-login-shell.event.gated-ended.*.*" {
|
||||
t.Fatalf("kept = %+v, want the stream SEAT_node-login-shell_gated-ended, last per subject, one day", kept)
|
||||
}
|
||||
marked := []string{"seat:node-login-shell.execute"}
|
||||
stream := "SEAT_node-login-shell_gated-ended"
|
||||
caller, err := PermissionsFor(Principal{Kind: KindModule, Node: "a", Module: "shop", Invokes: marked, MarkedVerbs: marked})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
own := "mesh.seat.node-login-shell.event.gated-ended.a=2eshop.>"
|
||||
if !slices.Contains(caller.Subscribe, own) || !slices.Contains(caller.Publish, "$JS.API.CONSUMER.CREATE."+stream+".*."+own) ||
|
||||
!slices.Contains(caller.Publish, "$JS.API.CONSUMER.MSG.NEXT."+stream+".*") {
|
||||
t.Errorf("the caller hears %v and reads %v, want its own ends alone", caller.Subscribe, caller.Publish)
|
||||
}
|
||||
other, _ := PermissionsFor(Principal{Kind: KindModule, Node: "a", Module: "bank", MarkedVerbs: marked})
|
||||
for _, s := range append(other.Subscribe, other.Publish...) {
|
||||
if slices.Contains([]string{own, "$JS.API.STREAM.INFO." + stream}, s) {
|
||||
t.Errorf("a module that does not invoke the marked verb is granted %s", s)
|
||||
}
|
||||
}
|
||||
proc, err := PermissionsFor(Principal{Kind: KindProcess, Node: "a", Module: "shop", Process: "run", Invokes: marked, MarkedVerbs: marked})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !slices.Contains(proc.Subscribe, "mesh.seat.node-login-shell.event.gated-ended.a=2eshop=2erun.>") {
|
||||
t.Errorf("a process hears %v, want its own account's ends", proc.Subscribe)
|
||||
}
|
||||
holder, err := PermissionsFor(Principal{Kind: KindProcess, Node: "a", Module: "shell", Process: "run", AnswersSeat: marked, MarkedVerbs: marked})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !slices.Contains(holder.Publish, "mesh.seat.node-login-shell.event.gated-ended.*.*") || slices.Contains(holder.Subscribe, own) {
|
||||
t.Errorf("the holder publishes %v, want gated-ended to any caller and no caller's hearing", holder.Publish)
|
||||
}
|
||||
}
|
||||
@@ -218,6 +218,9 @@ type Principal struct {
|
||||
Invokes []string
|
||||
// AnswersSeat are the seat verbs a process answers, each seat:<seat>.<verb> (design 47 G A-109), of a process with a bus on its own account, else of the module's own (A-115).
|
||||
AnswersSeat []string
|
||||
// MarkedVerbs are the marked verbs of its module's declaration (Declared.MarkedVerbs): those of them it invokes by name it
|
||||
// hears the ends of on gated-ended, and those it answers it says them on (design 47 G §677).
|
||||
MarkedVerbs []string
|
||||
|
||||
// AsksIssued are, for the vault's holder, the tools it asks in its own name for a credential a provider's software
|
||||
// issued, each `<module>.<tool>` (novox/hq ADR 0305, issue 723, vaultCalls): on the subject that names its caller,
|
||||
@@ -1107,6 +1110,9 @@ func permissionsFor(p Principal) (Permissions, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4a. The ends of its calls of a marked verb, on the seat's gated-ended, its own alone (design 47 G §677, A-264).
|
||||
gp, gs := gatedEndedGrants(p.Username(), p.MarkedVerbs, p.Invokes, p.AnswersSeat)
|
||||
pub, sub = append(pub, gp...), append(sub, gs...)
|
||||
|
||||
// 5. Its state, and the state of others it reads (novox/hq ADR 0201): every one read and
|
||||
// watched, its own written too.
|
||||
|
||||
@@ -79,6 +79,11 @@ func processGrants(p Principal) (pub, sub []string, err error) {
|
||||
}
|
||||
sub = append(sub, answered...)
|
||||
|
||||
// 5c. The ends of its calls of a marked verb, heard on the seat's gated-ended, its own alone; and those of the marked verbs
|
||||
// it answers, said to any caller (design 47 G §677, A-264).
|
||||
gp, gs := gatedEndedGrants(p.Username(), p.MarkedVerbs, p.Invokes, p.AnswersSeat)
|
||||
pub, sub = append(pub, gp...), append(sub, gs...)
|
||||
|
||||
// 6. The bus module's process (busstate.go, ADR 0595 §4, §5): every module's bus state it keeps, by name; its own
|
||||
// consumer of what its module consumes; its module's own stream's events; and its conditions through the seats its
|
||||
// module uses (the operator channel's accept.condition in its module's name).
|
||||
|
||||
@@ -115,6 +115,9 @@ type Declared struct {
|
||||
// ServesOwn are the seat verbs of this module's processes that declare no bus, each seat:<seat>.<verb>: such a process
|
||||
// runs on the module's own account, which is granted their subscribes (design 47 G A-115, A-127).
|
||||
ServesOwn []string
|
||||
// MarkedVerbs are the marked verbs (`seat:<seat>.<verb>`, definition-marked, design 47 G §677) the module or one of its
|
||||
// processes invokes or answers: each caller hears its own calls' ends on the seat's gated-ended (gatedEndedGrants).
|
||||
MarkedVerbs []string
|
||||
// Ledgers are the local names of the ledgers it asks for in `requires` (catalogue.StorageLedger); each assignment of a
|
||||
// module with any gets a ledger principal of its own (issue 1966 D5, ADR 0597 §9).
|
||||
Ledgers []string
|
||||
@@ -295,7 +298,7 @@ func Users(r Records) ([]Principal, error) {
|
||||
out = append(out, Principal{
|
||||
Kind: KindProcess, Node: node, Module: d.Module, Process: pr.Process,
|
||||
Emits: pr.Emits, Queues: pr.Queues, State: pr.State, Uses: d.Uses, Asks: pr.Asks,
|
||||
Invokes: pr.Invokes, AnswersSeat: pr.Serves, Removes: pr.Removes,
|
||||
Invokes: pr.Invokes, AnswersSeat: pr.Serves, Removes: pr.Removes, MarkedVerbs: d.MarkedVerbs,
|
||||
})
|
||||
// The process of the module holding mesh-broker whose bus declares `keeps` keeps every module's bus state
|
||||
// (busstate.go, ADR 0595 §4, ADR 0626 §1); `keeps` on any other module composes nothing (the judge refuses it,
|
||||
@@ -341,6 +344,7 @@ func Users(r Records) ([]Principal, error) {
|
||||
PerMachine: perMachineNames(d.State), ReadOnly: readOnlyNames(d.State), KeyedReads: d.KeyedReads,
|
||||
Logs: logNames(d.Logs), Streams: d.Streams, Hears: ComposedHears(d), Queues: d.Queues,
|
||||
Objects: objectNames(d.Objects), ReadsObjects: d.ReadsObjects, Bound: d.Bindings, AnswersSeat: d.ServesOwn,
|
||||
MarkedVerbs: d.MarkedVerbs,
|
||||
})
|
||||
if d.HoldsRecovery {
|
||||
out[len(out)-1].Recovers = r.RecoveryNames
|
||||
|
||||
@@ -184,6 +184,7 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio
|
||||
d.Marked = append(d.Marked, mark)
|
||||
}
|
||||
}
|
||||
d.MarkedVerbs = markedNamed(d, seats)
|
||||
d.CatchUps = catchUpsOf(m)
|
||||
if _, reads := m.OwnSecrets["broker"]; !reads {
|
||||
d.NoAccount = true
|
||||
@@ -431,6 +432,27 @@ func markedVerbs(m catalogue.Manifest, s catalogue.SeatDeclaration) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// markedNamed are the verbs a seat definition marks (design 46 approval mark §2; design 47 G §677) that the declaration's module
|
||||
// or one of its processes invokes by name or answers, each `seat:<seat>.<verb>`, sorted.
|
||||
func markedNamed(d buscompose.Declared, seats map[string]catalogue.SeatDeclaration) []string {
|
||||
named := slices.Concat(d.Invokes, d.ServesOwn)
|
||||
for _, pr := range d.Processes {
|
||||
named = append(named, slices.Concat(pr.Invokes, pr.Serves)...)
|
||||
}
|
||||
var out []string
|
||||
for _, v := range named {
|
||||
seat, verb, ok := catalogue.SeatInvoke(v)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if i := slices.IndexFunc(seats[seat].Serves, func(s catalogue.Verb) bool { return s.Name == verb }); i >= 0 && seats[seat].Serves[i].NeedsApproval {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
slices.Sort(out)
|
||||
return slices.Compact(out)
|
||||
}
|
||||
|
||||
func asSeat(s catalogue.SeatDeclaration, declarer string) buscompose.Seat {
|
||||
seat := buscompose.Seat{Name: s.Name, Scope: s.Scope, Accepts: s.Accepts, Emits: s.Emits,
|
||||
Serves: catalogue.VerbNames(s.Serves), Kinded: s.Kinded, ByCaller: s.ByCaller, Proofs: s.Proofs, DeclaredBy: declarer}
|
||||
|
||||
@@ -44,3 +44,19 @@ func TestSentStoresRead(t *testing.T) {
|
||||
t.Errorf("a used seat's verbs carry %v, a held seat no definition names %v", d.Uses[0].VerbCallers, d.Holds[0].VerbCallers)
|
||||
}
|
||||
}
|
||||
|
||||
// The marked verbs a declaration names (design 47 G §677): a definition-marked verb its module invokes or a process answers,
|
||||
// and no unmarked verb.
|
||||
func TestTheMarkedVerbsADeclarationNames(t *testing.T) {
|
||||
seats := map[string]catalogue.SeatDeclaration{"node-login-shell": {Name: "node-login-shell",
|
||||
Serves: []catalogue.Verb{{Name: "execute", NeedsApproval: true}, {Name: "list"}}}}
|
||||
d := buscompose.Declared{Invokes: []string{"seat:node-login-shell.execute", "seat:node-login-shell.list", "shop.tool"},
|
||||
Processes: []buscompose.ProcessPrincipal{{Serves: []string{"seat:node-login-shell.execute"}}}}
|
||||
if got := markedNamed(d, seats); !slices.Equal(got, []string{"seat:node-login-shell.execute"}) {
|
||||
t.Errorf("marked = %v", got)
|
||||
}
|
||||
m := catalogue.Manifest{Module: "shop", Invokes: []string{"seat:node-login-shell.execute"}}
|
||||
if got := declaredFor(m, seats, nil).MarkedVerbs; !slices.Equal(got, []string{"seat:node-login-shell.execute"}) {
|
||||
t.Errorf("the declaration carries %v, want the marked verb it invokes", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user