Area 2 (join 11): every seat verb and build-seat queue a delivery module calls is in its invokes
The grants part grants a seat verb only to a module that names it in invokes (GR8), so a call missing from a manifest is refused live. Listed every module's seat-verb calls in code (constant clients, seatlist.HolderOf, operatorask, buildrun's drops and puts) against its invokes: the teller asks who holds operator-channel (operatorask.Bucket) and the checker drops asks of the image build seat. Every asker of a build seat names the queues it asks as seat:<seat>.accept.<queue> (A-231): the runner check and delivery, the assigner check, the builder delivery, the checker check and recheck; the indexer had its entry. The validator judges that form through mesh-sdk's catalogue.QueueInvoke against the kinds the seat accepts. mesh-grants: a consumed seat view whose seat has no holder on record grants nothing instead of refusing the whole module (TestGrants); TestCallersAndWhyRefused and TestSentStoresRead take §610's class defaults. Builds: design 47 G §544, §601, §610, §616, A-231, A-179 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -144,6 +144,7 @@ func TestManifest(t *testing.T) {
|
||||
"seat:mesh-delivery.source", // §507: the branches the gate keeps a source revision of, before the first registrations
|
||||
"seat:mesh-hand-acts.hand-act", "seat:mesh-hand-acts.hand-acts", "seat:mesh-hand-acts.warranted", // ADR 0565 §9, §11: the seat alone
|
||||
"seat:node-backup.backed-up", "seat:node-backup.now",
|
||||
"seat:node-build-go.accept.check", // A-231: the composition run's ask on the Go build seat's queue check (accept.check.<asker>)
|
||||
"seat:node-build-go.output", // A-254: a composition run's answer kept as the build seat's object
|
||||
"seat:node-engine.compose-candidate", // §354, A-226: each machine's composition input
|
||||
"seat:node-engine.manifest-reads", // ADR 0618 §3
|
||||
|
||||
@@ -84,7 +84,8 @@
|
||||
"seat:node-build-go.output",
|
||||
"seat:node-engine.compose-candidate",
|
||||
"seat:node-engine.manifest-reads",
|
||||
"seat:operator-channel.asks"
|
||||
"seat:operator-channel.asks",
|
||||
"seat:node-build-go.accept.check"
|
||||
],
|
||||
"streams": [
|
||||
{
|
||||
|
||||
@@ -146,7 +146,9 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1
|
||||
if want := []string{"seat:mesh-catalog.entry", "seat:mesh-catalog.registrations", "seat:mesh-catalog.seat-definitions", "seat:mesh-catalog.revision", "seat:mesh-delivery.source", "seat:container-image-registry.publish", "seat:container-image-registry.remove", "seat:container-image-registry.versions",
|
||||
"seat:git.tag", "seat:git.tags",
|
||||
"seat:go-package-registry.publish", "seat:go-package-registry.remove", "seat:go-package-registry.versions", "seat:mesh-artifact-sweep.held", "seat:mesh-builds.reserved", "seat:mesh-catalog.versions", "seat:mesh-hand-acts.hand-act", "seat:mesh-module-assigner.assignments", "seat:mesh-watchdogs.presence", "seat:node-build-go.drop", "seat:node-build-image.drop", "seat:node-build-node.drop", "seat:node-engine.manifest-reads", "seat:npm-package-registry.publish", "seat:npm-package-registry.remove",
|
||||
"seat:npm-package-registry.versions", "seat:oci-artifact-registry.publish", "seat:oci-artifact-registry.remove", "seat:oci-artifact-registry.versions"}; !reflect.DeepEqual(sorted(m.Invokes), sorted(want)) {
|
||||
"seat:npm-package-registry.versions", "seat:oci-artifact-registry.publish", "seat:oci-artifact-registry.remove", "seat:oci-artifact-registry.versions",
|
||||
// A-231: the build asks on each build seat's queue delivery (accept.delivery.<asker>).
|
||||
"seat:node-build-go.accept.delivery", "seat:node-build-node.accept.delivery", "seat:node-build-image.accept.delivery"}; !reflect.DeepEqual(sorted(m.Invokes), sorted(want)) {
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
// The builder hears the build seats' toolchain-reported and the node-engine seat's running (design 47 G §492, §546) and reads no
|
||||
|
||||
@@ -106,7 +106,10 @@
|
||||
"seat:mesh-catalog.registrations",
|
||||
"seat:mesh-catalog.seat-definitions",
|
||||
"seat:mesh-catalog.revision",
|
||||
"seat:mesh-delivery.source"
|
||||
"seat:mesh-delivery.source",
|
||||
"seat:node-build-go.accept.delivery",
|
||||
"seat:node-build-node.accept.delivery",
|
||||
"seat:node-build-image.accept.delivery"
|
||||
],
|
||||
"state": [
|
||||
{ "name": "builds", "history": 5 },
|
||||
|
||||
@@ -132,7 +132,12 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
||||
if want := []string{"mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests",
|
||||
"mesh-delivery-indexer.indexer_read_modules", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_read_predefined",
|
||||
"mesh-delivery-indexer.indexer_read_seat_definitions",
|
||||
"seat:git.file", "seat:mesh-catalog.revision", "seat:mesh-catalog.seat-definitions", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", "seat:mesh-module-assigner.assignments", "seat:node-build-go.drop", "seat:node-build-node.drop", "seat:node-engine.compose-candidate"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
|
||||
"seat:git.file", "seat:mesh-catalog.revision", "seat:mesh-catalog.seat-definitions", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", "seat:mesh-module-assigner.assignments", "seat:node-build-go.drop", "seat:node-build-node.drop", "seat:node-engine.compose-candidate",
|
||||
// The image build seat's drop too: a check of a module built by it is asked of it, and dropped of it (buildrun.SeatDrops).
|
||||
"seat:node-build-image.drop",
|
||||
// A-231: the check asks on each build seat's queues check and recheck.
|
||||
"seat:node-build-go.accept.check", "seat:node-build-go.accept.recheck", "seat:node-build-node.accept.check",
|
||||
"seat:node-build-node.accept.recheck", "seat:node-build-image.accept.check", "seat:node-build-image.accept.recheck"}; !reflect.DeepEqual(sorted(m.Invokes), sorted(want)) {
|
||||
t.Errorf("invokes = %v, want the seat git's file, the seats' drop and compose-candidate", m.Invokes)
|
||||
}
|
||||
if want := []string{"node-build-go", "node-build-node"}; !reflect.DeepEqual(sorted(m.Uses), want) {
|
||||
|
||||
@@ -95,7 +95,14 @@
|
||||
"seat:mesh-module-assigner.assignments",
|
||||
"seat:node-build-go.drop",
|
||||
"seat:node-build-node.drop",
|
||||
"seat:node-engine.compose-candidate"
|
||||
"seat:node-engine.compose-candidate",
|
||||
"seat:node-build-image.drop",
|
||||
"seat:node-build-go.accept.check",
|
||||
"seat:node-build-go.accept.recheck",
|
||||
"seat:node-build-node.accept.check",
|
||||
"seat:node-build-node.accept.recheck",
|
||||
"seat:node-build-image.accept.check",
|
||||
"seat:node-build-image.accept.recheck"
|
||||
],
|
||||
|
||||
"own-secrets": {
|
||||
|
||||
@@ -4580,6 +4580,11 @@ func seatInvokeProblems(shelf catalogue.Shelf, declared map[string]catalogue.Sea
|
||||
problems = append(problems, fmt.Sprintf("%s invokes %s, a verb of the seat %q, "+catalogue.UndeclaredSeat, module, invoke, seat))
|
||||
continue
|
||||
}
|
||||
// An asker's queue of a build seat, `seat:<seat>.accept.<queue>` (design 47 G §544, §601, A-231), read by mesh-sdk's
|
||||
// one reader: judged against the kinds the seat's definition accepts.
|
||||
if _, queue, isQueue := catalogue.QueueInvoke(invoke); isQueue {
|
||||
verb = queue
|
||||
}
|
||||
if !slices.ContainsFunc(verbs, func(v catalogue.Verb) bool { return v.Name == verb }) && !acceptsKind(seat, verb, declared) {
|
||||
problems = append(problems, fmt.Sprintf("%s invokes %s, and the seat %s serves no verb %q", module, invoke, seat, verb))
|
||||
continue
|
||||
|
||||
@@ -6538,6 +6538,10 @@ func TestASeatNamedReadIsJudgedAgainstTheSeatsStates(t *testing.T) {
|
||||
{"seat:mesh-provisioner.check.step", ""},
|
||||
{"seat:mesh-provisioner.delivery.step", `serves no verb "delivery.step"`},
|
||||
{"seat:mesh-provisioner.check.other", `serves no verb "check.other"`},
|
||||
// A-231: an asker names the queue it asks as `seat:<seat>.accept.<queue>`, a kind the seat accepts.
|
||||
{"seat:mesh-provisioner.accept.step", ""},
|
||||
{"seat:mesh-provisioner.accept.other", `serves no verb "other"`},
|
||||
{"seat:mesh-provisioner.accept.check.step", `serves no verb "accept.check.step"`},
|
||||
} {
|
||||
shelf["caller"] = catalogue.Manifest{Module: "caller", Invokes: []string{row.invoke}}
|
||||
// The manifest's own invokes rule (at parse) takes the dotted kind as well; the seat's rule then judges it.
|
||||
|
||||
@@ -100,7 +100,10 @@ func TestManifest(t *testing.T) {
|
||||
"seat:mesh-builds.step", "seat:mesh-wants.step", "seat:node-build-go.check.step", "seat:node-build-node.check.step",
|
||||
"seat:node-build-image.check.step",
|
||||
// RN22 (§86, §415, A-207): a recovery's steps on the build seats' stream delivery.
|
||||
"seat:node-build-go.delivery.step", "seat:node-build-node.delivery.step", "seat:node-build-image.delivery.step"})},
|
||||
"seat:node-build-go.delivery.step", "seat:node-build-node.delivery.step", "seat:node-build-image.delivery.step",
|
||||
// A-231: the queues of the build seats it asks, check for the steps and delivery for a recovery's.
|
||||
"seat:node-build-go.accept.check", "seat:node-build-go.accept.delivery", "seat:node-build-node.accept.check",
|
||||
"seat:node-build-node.accept.delivery", "seat:node-build-image.accept.check", "seat:node-build-image.accept.delivery"})},
|
||||
{"uses operator-channel", equalSets(m.Uses, []string{"operator-channel"})},
|
||||
// RN25 (§539): the runner's tools carry the prefix runner_.
|
||||
{"first-view tools", equalSets(m.Tools, []string{"runner_first_views", "runner_first_view_again", "runner_health", "runner_waits", "runner_release", "runner_unlogged", "runner_unlogged_append"})},
|
||||
|
||||
@@ -73,7 +73,13 @@
|
||||
"seat:node-build-image.delivery.step",
|
||||
"seat:node-build-go.drop",
|
||||
"seat:node-build-node.drop",
|
||||
"seat:node-build-image.drop"
|
||||
"seat:node-build-image.drop",
|
||||
"seat:node-build-go.accept.check",
|
||||
"seat:node-build-go.accept.delivery",
|
||||
"seat:node-build-node.accept.check",
|
||||
"seat:node-build-node.accept.delivery",
|
||||
"seat:node-build-image.accept.check",
|
||||
"seat:node-build-image.accept.delivery"
|
||||
],
|
||||
"uses": [
|
||||
"operator-channel"
|
||||
|
||||
@@ -104,7 +104,9 @@ func TestManifestDeclarations(t *testing.T) { // 1240/AC1
|
||||
}
|
||||
// Its own states answers and teller-marks are said as its views family views.answers, read by teller_read_answers (A-175, A-178).
|
||||
// The join: its first view asked again by hand is a hand act, recorded through the seat (views.StatusTools).
|
||||
if want := []string{"mesh-delivery-assigner.assigner_read_asks", "mesh-delivery-view.view_read_answers", "seat:mesh-hand-acts.hand-act", "seat:mesh-catalog.registrations", "seat:mesh-broker.show", "seat:operator-channel.notify"}; !reflect.DeepEqual(m.Invokes, want) {
|
||||
if want := []string{"mesh-delivery-assigner.assigner_read_asks", "mesh-delivery-view.view_read_answers", "seat:mesh-hand-acts.hand-act", "seat:mesh-catalog.registrations", "seat:mesh-broker.show", "seat:operator-channel.notify",
|
||||
// A-126: who holds the seat operator-channel, whose state asks holds the router's record of its asks (operatorask.Bucket).
|
||||
"seat:mesh-module-assigner.assignments"}; !reflect.DeepEqual(m.Invokes, want) {
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
// design 47 G §335 (TL1): the widening of a pipeline end is told through the seat operator-channel and kept in widens-said.
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"seat:mesh-hand-acts.hand-act",
|
||||
"seat:mesh-catalog.registrations",
|
||||
"seat:mesh-broker.show",
|
||||
"seat:operator-channel.notify"
|
||||
"seat:operator-channel.notify",
|
||||
"seat:mesh-module-assigner.assignments"
|
||||
],
|
||||
"consumes": [
|
||||
"mesh-conditions.findings-asked",
|
||||
|
||||
@@ -874,6 +874,9 @@ func permissionsFor(p Principal) (Permissions, error) {
|
||||
// And the events the node-engine's manifest consumes (the backup holder's word that it backed the module up, ADR 0539
|
||||
// clause 16; the status view's `settled`, design 51): by the emitter's own subject, as any consumer's.
|
||||
for _, c := range p.Consumes {
|
||||
if unresolvedSeatView(c) {
|
||||
continue
|
||||
}
|
||||
subject, err := consumedSubject(c)
|
||||
if err != nil {
|
||||
return Permissions{}, err
|
||||
@@ -1014,6 +1017,9 @@ func permissionsFor(p Principal) (Permissions, error) {
|
||||
// 2. What it consumes, by the emitter's own subject — an event is addressed to its
|
||||
// emitter, because the emitter's identity is the meaning (ADR 0118).
|
||||
for _, c := range p.Consumes {
|
||||
if unresolvedSeatView(c) {
|
||||
continue
|
||||
}
|
||||
subject, err := consumedSubject(c)
|
||||
if err != nil {
|
||||
return Permissions{}, err
|
||||
@@ -1490,6 +1496,15 @@ func consumedSubject(pattern string) (string, error) {
|
||||
return "mesh.mod." + emitter + ".event." + strings.Join(out, "."), nil
|
||||
}
|
||||
|
||||
// unresolvedSeatView is a `consumes` entry still naming a seat's view as `seat:<seat>.views.<state>`: the grants part
|
||||
// resolves it to the seat's holder on record, and one whose seat has no holder on record stays as written and grants
|
||||
// nothing (design 47 G §616), rather than refusing the whole module as a malformed event would.
|
||||
func unresolvedSeatView(c string) bool {
|
||||
rest, isSeat := strings.CutPrefix(c, "seat:")
|
||||
_, view, named := strings.Cut(rest, ".views.")
|
||||
return isSeat && named && view != ""
|
||||
}
|
||||
|
||||
// catalogueTheRest is the mesh's wildcard for "the rest of a name", duplicated from the catalogue
|
||||
// package for the one direction of dependency the build queue's name is duplicated for.
|
||||
const catalogueTheRest = "**"
|
||||
|
||||
@@ -23,16 +23,18 @@ func (c *canCall) Call(key string, body any) (json.RawMessage, error) {
|
||||
return json.Marshal(map[string]any{"allowed": c.allowed})
|
||||
}
|
||||
|
||||
// grants_callers and grants_why_refused read the definitions' callers (design 47 G §83): a verb that names none takes its
|
||||
// class's default, and a refused account is told the call subject it lacks and the callers the definition admits.
|
||||
// grants_callers and grants_why_refused read the definitions' callers (design 47 G §83): every verb takes its class's
|
||||
// default (§610: no verb names its callers; an act's are the operator, sessions and invokers, a read's invokers, persons
|
||||
// and sessions), and a refused account is told the call subject it lacks and the callers the definition admits.
|
||||
func TestCallersAndWhyRefused(t *testing.T) {
|
||||
defs := map[string]seatdef.Definition{"mesh-wants": {Seat: "mesh-wants", Scope: "mesh", Verbs: []seatdef.Verb{
|
||||
{Name: "upgrade", Class: seatdef.ClassAct},
|
||||
{Name: "entry", Callers: &seatdef.Callers{List: []string{"seat:mesh-pipeline-runner", "operator"}}},
|
||||
{Name: "entry", Class: seatdef.ClassRead},
|
||||
}}}
|
||||
lines := CallerLines(defs, "mesh-wants", "")
|
||||
if len(lines) != 2 || lines[0].Verb != "entry" || !lines[0].Named || lines[1].Named ||
|
||||
strings.Join(lines[1].Callers, ",") != "operator,sessions" {
|
||||
if len(lines) != 2 || lines[0].Verb != "entry" || lines[0].Named || lines[1].Named ||
|
||||
strings.Join(lines[0].Callers, ",") != "invokers,persons,sessions" ||
|
||||
strings.Join(lines[1].Callers, ",") != "operator,sessions,invokers" {
|
||||
t.Fatalf("lines: %+v", lines)
|
||||
}
|
||||
can := &canCall{}
|
||||
@@ -43,7 +45,7 @@ func TestCallersAndWhyRefused(t *testing.T) {
|
||||
}
|
||||
out := got.(map[string]any)
|
||||
if out["lacks"] != "mesh.seat.mesh-wants.call.upgrade.one~shop" || can.asked["subject"] != out["lacks"] ||
|
||||
!strings.Contains(out["says"].(string), "operator, sessions") {
|
||||
!strings.Contains(out["says"].(string), "operator, sessions, invokers") {
|
||||
t.Errorf("a refused caller: %+v", out)
|
||||
}
|
||||
can.allowed = true
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestSentStoresRead(t *testing.T) {
|
||||
Invokes: []string{"seat:orders-seat.place"}}, []string{"shop_sent-objects"}},
|
||||
{"invokes a verb that names it", catalogue.Manifest{Module: "plain", Invokes: []string{"seat:orders-seat.place"}}, []string{"shop_sent-objects"}},
|
||||
{"invokes a read, its callers include invokers", catalogue.Manifest{Module: "bank", Invokes: []string{"seat:orders-seat.list"}}, []string{"shop_sent-objects"}},
|
||||
{"invokes an act no one names it for", catalogue.Manifest{Module: "bank", Invokes: []string{"seat:orders-seat.wipe"}}, nil},
|
||||
{"invokes an act, its callers include invokers (§610)", catalogue.Manifest{Module: "bank", Invokes: []string{"seat:orders-seat.wipe"}}, []string{"shop_sent-objects"}},
|
||||
{"invokes a verb that names others", catalogue.Manifest{Module: "bank", Invokes: []string{"seat:orders-seat.place"}}, nil},
|
||||
{"declares neither", catalogue.Manifest{Module: "bank"}, nil},
|
||||
{"its own store is its binding's", holder, nil},
|
||||
|
||||
Reference in New Issue
Block a user