Merge pull request 'Registry recovery: mesh-broker's list, show and orphans answer each binding (issue 1966)' (#5) from fix/1966-bindings-first-view into main

This commit was merged in pull request #5.
This commit is contained in:
2026-11-11 10:32:31 +00:00
6 changed files with 226 additions and 25 deletions
+25 -7
View File
@@ -72,16 +72,32 @@ const SubjectRecreated = "mesh.seat.mesh-broker.event.recreated"
// SubjectDropped is where dropped is published. // SubjectDropped is where dropped is published.
const SubjectDropped = "mesh.seat.mesh-broker.event.dropped" const SubjectDropped = "mesh.seat.mesh-broker.event.dropped"
// Binding is the binding a module receives in its membership for one entry (ADR 0595 §1, §7). // Binding is the binding a module receives in its membership for one entry (ADR 0595 §1, §7): the names on the bus of the entry's objects, one form in the events, in list, show and orphans, and in the membership.
type Binding struct { type Binding struct {
// the entry's name on the bus // a key-value bucket's name
BusName string `json:"bus-name"` Bucket string `json:"bucket,omitempty"`
// how the bus keeps it // a ledger's history stream
StorageType string `json:"storage-type"` History string `json:"history,omitempty"`
// its subjects, for a stream // a ledger's index buckets, by index name
Subjects []string `json:"subjects,omitempty"` Indexes map[string]string `json:"indexes,omitempty"`
// the entry's local name in the module's manifest
Local string `json:"local"`
// an object store's name
ObjectStore string `json:"object-store,omitempty"`
// the subject of a ledger's instances' snapshots
Snapshot string `json:"snapshot,omitempty"`
// a ledger's state bucket
State string `json:"state,omitempty"`
// how the bus keeps it: the entry's storage type
Storage string `json:"storage"`
// a stream's name on the bus
Stream string `json:"stream,omitempty"`
// the subject a stream's messages are on; also a ledger's history's
Subject string `json:"subject,omitempty"`
// its template // its template
Template string `json:"template,omitempty"` Template string `json:"template,omitempty"`
// a ledger's types, as its manifest declares them
Types []map[string]any `json:"types,omitempty"`
} }
// Entry is one piece of bus state as a manifest asks it. // Entry is one piece of bus state as a manifest asks it.
@@ -131,6 +147,8 @@ type Fill struct {
// Held is one entry as the bus holds it. // Held is one entry as the bus holds it.
type Held struct { type Held struct {
// the binding a module receives for the entry, as created and recreated carry it; present on an entry whose objects the bus holds, absent on one it does not (design 25 bindings-first-view §2)
Binding *Binding `json:"binding,omitempty"`
// its name on the bus, <module>_<local name> // its name on the bus, <module>_<local name>
BusName string `json:"bus-name"` BusName string `json:"bus-name"`
// its own configuration, where no template fits // its own configuration, where no template fits
+4
View File
@@ -19,6 +19,8 @@ func TestFixtures(t *testing.T) {
}{ }{
{name: "created on the seat's subject", subject: "mesh.seat.mesh-broker.event.created", given: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\"}", wire: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\"}", {name: "created on the seat's subject", subject: "mesh.seat.mesh-broker.event.created", given: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\"}", wire: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\"}",
decode: func(b []byte) (any, error) { var v CreatedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-broker\\.event\\.created$")}, decode: func(b []byte) (any, error) { var v CreatedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-broker\\.event\\.created$")},
{name: "created with a ledger's binding as the bus module writes it", subject: "mesh.seat.mesh-broker.event.created", given: "{\"binding\":{\"history\":\"mesh-knowledge_ledger\",\"indexes\":{\"by-kind\":\"mesh-knowledge_ledger_index-by-kind\"},\"local\":\"ledger\",\"snapshot\":\"mesh.ledger.mesh-knowledge.ledger.snapshot\",\"state\":\"mesh-knowledge_ledger_state\",\"storage\":\"ledger\",\"subject\":\"mesh.ledger.mesh-knowledge.ledger.history\",\"template\":\"ledger\",\"types\":[{\"indexes\":[{\"name\":\"by-kind\",\"on\":[\"kind\"]}],\"name\":\"decision\",\"operations\":[{\"effect\":\"set-fields\",\"fields\":[\"title\",\"kind\"],\"name\":\"set\"}],\"schema\":{\"kind\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"}}}]},\"bus-name\":\"mesh-knowledge_ledger\",\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"revision\":7,\"storage-type\":\"ledger\",\"template\":\"ledger\"}", wire: "{\"binding\":{\"history\":\"mesh-knowledge_ledger\",\"indexes\":{\"by-kind\":\"mesh-knowledge_ledger_index-by-kind\"},\"local\":\"ledger\",\"snapshot\":\"mesh.ledger.mesh-knowledge.ledger.snapshot\",\"state\":\"mesh-knowledge_ledger_state\",\"storage\":\"ledger\",\"subject\":\"mesh.ledger.mesh-knowledge.ledger.history\",\"template\":\"ledger\",\"types\":[{\"indexes\":[{\"name\":\"by-kind\",\"on\":[\"kind\"]}],\"name\":\"decision\",\"operations\":[{\"effect\":\"set-fields\",\"fields\":[\"title\",\"kind\"],\"name\":\"set\"}],\"schema\":{\"kind\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"}}}]},\"bus-name\":\"mesh-knowledge_ledger\",\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"revision\":7,\"storage-type\":\"ledger\",\"template\":\"ledger\"}",
decode: func(b []byte) (any, error) { var v CreatedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-broker\\.event\\.created$")},
{name: "refused on the seat's subject", subject: "mesh.seat.mesh-broker.event.refused", given: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}", wire: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}", {name: "refused on the seat's subject", subject: "mesh.seat.mesh-broker.event.refused", given: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}", wire: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}",
decode: func(b []byte) (any, error) { var v RefusedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-broker\\.event\\.refused$")}, decode: func(b []byte) (any, error) { var v RefusedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-broker\\.event\\.refused$")},
{name: "drifted on the seat's subject", subject: "mesh.seat.mesh-broker.event.drifted", given: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}", wire: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}", {name: "drifted on the seat's subject", subject: "mesh.seat.mesh-broker.event.drifted", given: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}", wire: "{\"asked\":{\"max-mib\":64},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"held\":{\"max-mib\":64},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"storage-type\":\"ledger\",\"template\":\"ledger\",\"why\":\"the bus refused it\"}",
@@ -41,6 +43,8 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "list answers every entry", subject: "", given: "{\"entries\":[{\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"options\":{\"max-mib\":64},\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}", wire: "{\"entries\":[{\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"options\":{\"max-mib\":64},\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}", {name: "list answers every entry", subject: "", given: "{\"entries\":[{\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"options\":{\"max-mib\":64},\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}", wire: "{\"entries\":[{\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"options\":{\"max-mib\":64},\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}",
decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "list answers the binding of an entry the bus holds", subject: "", given: "{\"entries\":[{\"binding\":{\"history\":\"mesh-knowledge_ledger\",\"indexes\":{\"by-kind\":\"mesh-knowledge_ledger_index-by-kind\"},\"local\":\"ledger\",\"snapshot\":\"mesh.ledger.mesh-knowledge.ledger.snapshot\",\"state\":\"mesh-knowledge_ledger_state\",\"storage\":\"ledger\",\"subject\":\"mesh.ledger.mesh-knowledge.ledger.history\",\"template\":\"ledger\",\"types\":[{\"indexes\":[{\"name\":\"by-kind\",\"on\":[\"kind\"]}],\"name\":\"decision\",\"operations\":[{\"effect\":\"set-fields\",\"fields\":[\"title\",\"kind\"],\"name\":\"set\"}],\"schema\":{\"kind\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"}}}]},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}", wire: "{\"entries\":[{\"binding\":{\"history\":\"mesh-knowledge_ledger\",\"indexes\":{\"by-kind\":\"mesh-knowledge_ledger_index-by-kind\"},\"local\":\"ledger\",\"snapshot\":\"mesh.ledger.mesh-knowledge.ledger.snapshot\",\"state\":\"mesh-knowledge_ledger_state\",\"storage\":\"ledger\",\"subject\":\"mesh.ledger.mesh-knowledge.ledger.history\",\"template\":\"ledger\",\"types\":[{\"indexes\":[{\"name\":\"by-kind\",\"on\":[\"kind\"]}],\"name\":\"decision\",\"operations\":[{\"effect\":\"set-fields\",\"fields\":[\"title\",\"kind\"],\"name\":\"set\"}],\"schema\":{\"kind\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"}}}]},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}",
decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
} }
for _, c := range cases { for _, c := range cases {
v, err := c.decode([]byte(c.given)) v, err := c.decode([]byte(c.given))
+56
View File
@@ -22,6 +22,62 @@
} }
} }
}, },
{
"name": "created with a ledger's binding as the bus module writes it",
"of": "event:created",
"subject": "mesh.seat.mesh-broker.event.created",
"given": {
"module": "mesh-knowledge",
"local-name": "ledger",
"storage-type": "ledger",
"template": "ledger",
"bus-name": "mesh-knowledge_ledger",
"binding": {
"local": "ledger",
"storage": "ledger",
"template": "ledger",
"subject": "mesh.ledger.mesh-knowledge.ledger.history",
"history": "mesh-knowledge_ledger",
"state": "mesh-knowledge_ledger_state",
"indexes": {
"by-kind": "mesh-knowledge_ledger_index-by-kind"
},
"snapshot": "mesh.ledger.mesh-knowledge.ledger.snapshot",
"types": [
{
"name": "decision",
"schema": {
"title": {
"type": "string"
},
"kind": {
"type": "string"
}
},
"operations": [
{
"name": "set",
"effect": "set-fields",
"fields": [
"title",
"kind"
]
}
],
"indexes": [
{
"name": "by-kind",
"on": [
"kind"
]
}
]
}
]
},
"revision": 7
}
},
{ {
"name": "refused on the seat's subject", "name": "refused on the seat's subject",
"of": "event:refused", "of": "event:refused",
+66
View File
@@ -47,5 +47,71 @@
} }
] ]
} }
},
{
"name": "list answers the binding of an entry the bus holds",
"of": "verb:list.answer",
"given": {
"entries": [
{
"module": "mesh-knowledge",
"local-name": "ledger",
"storage-type": "ledger",
"template": "ledger",
"bus-name": "mesh-knowledge_ledger",
"settings": {
"max-mib": 64
},
"fill": {
"count": 10,
"bytes": 2048,
"cap": 67108864
},
"binding": {
"local": "ledger",
"storage": "ledger",
"template": "ledger",
"subject": "mesh.ledger.mesh-knowledge.ledger.history",
"history": "mesh-knowledge_ledger",
"state": "mesh-knowledge_ledger_state",
"indexes": {
"by-kind": "mesh-knowledge_ledger_index-by-kind"
},
"snapshot": "mesh.ledger.mesh-knowledge.ledger.snapshot",
"types": [
{
"name": "decision",
"schema": {
"title": {
"type": "string"
},
"kind": {
"type": "string"
}
},
"operations": [
{
"name": "set",
"effect": "set-fields",
"fields": [
"title",
"kind"
]
}
],
"indexes": [
{
"name": "by-kind",
"on": [
"kind"
]
}
]
}
]
}
}
]
}
} }
] ]
+50 -11
View File
@@ -403,6 +403,10 @@
"why": { "why": {
"type": "string", "type": "string",
"description": "why it is in its state, in plain words; empty when held as asked" "description": "why it is in its state, in plain words; empty when held as asked"
},
"binding": {
"$ref": "#/defs/Binding",
"description": "the binding a module receives for the entry, as created and recreated carry it; present on an entry whose objects the bus holds, absent on one it does not (design 25 bindings-first-view §2)"
} }
}, },
"required": [ "required": [
@@ -540,31 +544,66 @@
"Binding": { "Binding": {
"type": "object", "type": "object",
"properties": { "properties": {
"bus-name": { "local": {
"type": "string", "type": "string",
"description": "the entry's name on the bus" "description": "the entry's local name in the module's manifest"
}, },
"storage-type": { "storage": {
"type": "string", "type": "string",
"description": "how the bus keeps it" "description": "how the bus keeps it: the entry's storage type"
}, },
"template": { "template": {
"type": "string", "type": "string",
"description": "its template" "description": "its template"
}, },
"subjects": { "stream": {
"type": "array", "type": "string",
"items": { "description": "a stream's name on the bus"
},
"subject": {
"type": "string",
"description": "the subject a stream's messages are on; also a ledger's history's"
},
"bucket": {
"type": "string",
"description": "a key-value bucket's name"
},
"object-store": {
"type": "string",
"description": "an object store's name"
},
"history": {
"type": "string",
"description": "a ledger's history stream"
},
"state": {
"type": "string",
"description": "a ledger's state bucket"
},
"indexes": {
"type": "object",
"additionalProperties": {
"type": "string" "type": "string"
}, },
"description": "its subjects, for a stream" "description": "a ledger's index buckets, by index name"
},
"snapshot": {
"type": "string",
"description": "the subject of a ledger's instances' snapshots"
},
"types": {
"type": "array",
"items": {
"type": "object"
},
"description": "a ledger's types, as its manifest declares them"
} }
}, },
"required": [ "required": [
"bus-name", "local",
"storage-type" "storage"
], ],
"description": "the binding a module receives in its membership for one entry (ADR 0595 §1, §7)" "description": "the binding a module receives in its membership for one entry (ADR 0595 §1, §7): the names on the bus of the entry's objects, one form in the events, in list, show and orphans, and in the membership"
} }
} }
} }
+25 -7
View File
@@ -47,16 +47,32 @@ export const recreatedSubject = "mesh.seat.mesh-broker.event.recreated";
export const droppedSubject = "mesh.seat.mesh-broker.event.dropped"; export const droppedSubject = "mesh.seat.mesh-broker.event.dropped";
/** the binding a module receives in its membership for one entry (ADR 0595 §1, §7) */ /** the binding a module receives in its membership for one entry (ADR 0595 §1, §7): the names on the bus of the entry's objects, one form in the events, in list, show and orphans, and in the membership */
export interface Binding { export interface Binding {
/** the entry's name on the bus */ /** a key-value bucket's name */
"bus-name": string; "bucket"?: string;
/** how the bus keeps it */ /** a ledger's history stream */
"storage-type": string; "history"?: string;
/** its subjects, for a stream */ /** a ledger's index buckets, by index name */
"subjects"?: string[]; "indexes"?: Record<string, string>;
/** the entry's local name in the module's manifest */
"local": string;
/** an object store's name */
"object-store"?: string;
/** the subject of a ledger's instances' snapshots */
"snapshot"?: string;
/** a ledger's state bucket */
"state"?: string;
/** how the bus keeps it: the entry's storage type */
"storage": string;
/** a stream's name on the bus */
"stream"?: string;
/** the subject a stream's messages are on; also a ledger's history's */
"subject"?: string;
/** its template */ /** its template */
"template"?: string; "template"?: string;
/** a ledger's types, as its manifest declares them */
"types"?: Record<string, unknown>[];
} }
/** one piece of bus state as a manifest asks it */ /** one piece of bus state as a manifest asks it */
@@ -86,6 +102,8 @@ export interface Fill {
/** one entry as the bus holds it */ /** one entry as the bus holds it */
export interface Held { export interface Held {
/** the binding a module receives for the entry, as created and recreated carry it; present on an entry whose objects the bus holds, absent on one it does not (design 25 bindings-first-view §2) */
"binding"?: Binding;
/** its name on the bus, <module>_<local name> */ /** its name on the bus, <module>_<local name> */
"bus-name": string; "bus-name": string;
/** its own configuration, where no template fits */ /** its own configuration, where no template fits */