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:
2026-11-12 12:17:50 +01:00
parent 4f076c6cc3
commit 4194e93a67
15 changed files with 73 additions and 16 deletions
@@ -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
+2 -1
View File
@@ -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
+4 -1
View File
@@ -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) {
+8 -1
View File
@@ -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"})},
+7 -1
View File
@@ -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.
+2 -1
View File
@@ -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},