Check a ledger operation against the types its binding carries, refusing a ledger bound with none (issue 1966 T2)
This commit is contained in:
@@ -67,7 +67,9 @@ func TestALedgerIsWrittenOnlyOnItsModulesLedgerConnection(t *testing.T) {
|
||||
}
|
||||
mem := mt.MembershipOf("m", "anchor", false, nil)
|
||||
mem.Bindings = []membership.BusBinding{{Local: "records", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger,
|
||||
History: "LEDGER_m_records", Subject: "mesh.ledger.m.records", State: "m_records", Snapshot: "mesh.ledger-snapshot.m.records"}}
|
||||
History: "LEDGER_m_records", Subject: "mesh.ledger.m.records", State: "m_records", Snapshot: "mesh.ledger-snapshot.m.records",
|
||||
Types: []catalogue.LedgerType{{Name: "decision", Operations: []catalogue.LedgerOperation{
|
||||
{Name: "title", Effect: catalogue.EffectSetFields, Fields: []string{"title"}}}}}}}
|
||||
raw, _ := json.Marshal(mem)
|
||||
var sdk membership.Membership
|
||||
_ = json.Unmarshal(raw, &sdk)
|
||||
@@ -96,12 +98,7 @@ func TestALedgerIsWrittenOnlyOnItsModulesLedgerConnection(t *testing.T) {
|
||||
}
|
||||
creds := FollowLedgerCredentials(file, bus.Dial, t.Logf)
|
||||
t.Cleanup(creds.Stop)
|
||||
types := func(string, string) ([]catalogue.LedgerType, error) {
|
||||
return []catalogue.LedgerType{{Name: "decision", Operations: []catalogue.LedgerOperation{
|
||||
{Name: "title", Effect: catalogue.EffectSetFields, Fields: []string{"title"}}}}}, nil
|
||||
}
|
||||
all := &consumers{conn: conn, logf: t.Logf, of: map[string]*moduleEvents{}}
|
||||
WithLedgerTypes(types)(all)
|
||||
WithLedgerWriters(creds.Writer)(all)
|
||||
m := all.forModule("m").(*moduleBus)
|
||||
apply := func(instance string) error {
|
||||
|
||||
@@ -78,10 +78,17 @@ func TestTheLedgerKeepsItsPromisesOnTheBus(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
types := []catalogue.LedgerType{{Name: "decision", Schema: map[string]catalogue.LedgerField{"title": {Type: "string"}},
|
||||
Operations: []catalogue.LedgerOperation{
|
||||
{Name: "title", Effect: catalogue.EffectSetFields, Fields: []string{"title"}, Author: true},
|
||||
{Name: "note", Effect: catalogue.EffectAppend, Into: "notes", Fields: []string{"says"}},
|
||||
},
|
||||
Indexes: []catalogue.LedgerIndex{{Name: "by-title", On: []string{"title"}}}}}
|
||||
binding := membership.BusBinding{Local: "records", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger,
|
||||
History: "LEDGER_m_records", Subject: "mesh.ledger.m.records", State: "m_records",
|
||||
Indexes: map[string]string{"by-title": "m_records_by_title"}, Snapshot: "mesh.ledger-snapshot.m.records"}
|
||||
mem := mt.MembershipOf("m", "anchor", false, nil)
|
||||
binding.Types = types
|
||||
mem.Bindings = []membership.BusBinding{binding}
|
||||
mesh.Issue(t, mem)
|
||||
conn := connect(t, "node-tools", "anchor")
|
||||
@@ -92,14 +99,7 @@ func TestTheLedgerKeepsItsPromisesOnTheBus(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
types := []catalogue.LedgerType{{Name: "decision", Schema: map[string]catalogue.LedgerField{"title": {Type: "string"}},
|
||||
Operations: []catalogue.LedgerOperation{
|
||||
{Name: "title", Effect: catalogue.EffectSetFields, Fields: []string{"title"}, Author: true},
|
||||
{Name: "note", Effect: catalogue.EffectAppend, Into: "notes", Fields: []string{"says"}},
|
||||
},
|
||||
Indexes: []catalogue.LedgerIndex{{Name: "by-title", On: []string{"title"}}}}}
|
||||
all := &consumers{conn: conn, logf: t.Logf, of: map[string]*moduleEvents{}}
|
||||
WithLedgerTypes(func(string, string) ([]catalogue.LedgerType, error) { return types, nil })(all)
|
||||
WithLedgerWriters(func(string) (*bus.Conn, error) { return conn, nil })(all)
|
||||
m := all.forModule("m").(*moduleBus)
|
||||
|
||||
|
||||
@@ -20,21 +20,9 @@ import (
|
||||
// for the module whose bundle or whose repair tool asks, from its own bindings alone, with its types and the connection
|
||||
// its history is published on.
|
||||
|
||||
// LedgerTypes is where the runner reads a ledger's types, by module and local name. Where it reads them from on the
|
||||
// live mesh is not decided (issue 1966: the binding carries no types, and no record names a source); until it is,
|
||||
// the runner refuses to open a ledger and says why, and a test gives the types.
|
||||
type LedgerTypes func(module, local string) ([]catalogue.LedgerType, error)
|
||||
|
||||
// errTypesUndecided is the refusal of a ledger whose types the runner has no source for.
|
||||
func errTypesUndecided(module, local string) error {
|
||||
return fmt.Errorf("the tool runner cannot open %s's ledger %q: it reads a ledger's types from nowhere yet, since its "+
|
||||
"binding carries none and no record names their source (issue 1966)", module, local)
|
||||
}
|
||||
|
||||
// ledgerSource is how the runner opens a module's ledger: its types, and the connection its history and snapshots are
|
||||
// published on (the module's ledger credential, ADR 0597 §9).
|
||||
// ledgerSource is how the runner opens a module's ledger beside its binding: the connection its history and snapshots
|
||||
// are published on (the module's ledger credential, ADR 0597 §9).
|
||||
type ledgerSource struct {
|
||||
types LedgerTypes
|
||||
writer func(module string) (*bus.Conn, error)
|
||||
}
|
||||
|
||||
@@ -97,13 +85,12 @@ func (c *consumers) ledger(module, local string) (*ledger.Ledger, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
types := c.ledgers.types
|
||||
if types == nil {
|
||||
return nil, errTypesUndecided(module, local)
|
||||
}
|
||||
ts, err := types(module, local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Its types travel on its binding, copied by the grants part from the entry the manifest-accepted event carries
|
||||
// (mesh-sdk membership.BusBinding.Types): the runner reads no other part's state for them (ADR 0596).
|
||||
ts := b.Types
|
||||
if len(ts) == 0 {
|
||||
return nil, fmt.Errorf("%s's ledger %q is bound with no types, so no operation can be checked against one: its "+
|
||||
"binding carries the types its manifest asks for in `requires` (novox/hq ADR 0597 §1)", module, local)
|
||||
}
|
||||
var writer *bus.Conn
|
||||
if c.ledgers.writer != nil {
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestTheLedgerRepairsAreToolsOfEveryLedgerOwner(t *testing.T) {
|
||||
}
|
||||
|
||||
// aLedger raises one ledger of module m on the bus as the bus module would create it, binds it in m's membership, and
|
||||
// answers the runtime's buses for m and for other, its types given as a test gives them.
|
||||
// answers the runtime's buses for m and for other; its types travel on its binding, and a second binding carries none.
|
||||
func aLedger(t *testing.T) (m, other *moduleBus, mesh *mt.Mesh) {
|
||||
t.Helper()
|
||||
mesh = mt.New(t)
|
||||
@@ -103,7 +103,11 @@ func aLedger(t *testing.T) (m, other *moduleBus, mesh *mt.Mesh) {
|
||||
}
|
||||
mem := mt.MembershipOf("m", "anchor", false, nil)
|
||||
mem.Bindings = []membership.BusBinding{{Local: "records", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger,
|
||||
History: "LEDGER_m_records", Subject: "mesh.ledger.m.records", State: "m_records", Snapshot: "mesh.ledger-snapshot.m.records"}}
|
||||
History: "LEDGER_m_records", Subject: "mesh.ledger.m.records", State: "m_records", Snapshot: "mesh.ledger-snapshot.m.records",
|
||||
Types: []catalogue.LedgerType{{Name: "decision", Schema: map[string]catalogue.LedgerField{"title": {Type: "string"}},
|
||||
Operations: []catalogue.LedgerOperation{{Name: "title", Effect: catalogue.EffectSetFields, Fields: []string{"title"}, Author: true}}}}},
|
||||
{Local: "untyped", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger, History: "LEDGER_m_records",
|
||||
Subject: "mesh.ledger.m.records", State: "m_records", Snapshot: "mesh.ledger-snapshot.m.records"}}
|
||||
mesh.Issue(t, mem)
|
||||
mesh.Issue(t, mt.MembershipOf("other", "anchor", false, nil))
|
||||
conn := connect(t, "node-tools", "anchor")
|
||||
@@ -116,10 +120,6 @@ func aLedger(t *testing.T) (m, other *moduleBus, mesh *mt.Mesh) {
|
||||
return nil
|
||||
})
|
||||
all := &consumers{conn: conn, logf: t.Logf, of: map[string]*moduleEvents{}, ledgers: ledgerSource{
|
||||
types: func(string, string) ([]catalogue.LedgerType, error) {
|
||||
return []catalogue.LedgerType{{Name: "decision", Schema: map[string]catalogue.LedgerField{"title": {Type: "string"}},
|
||||
Operations: []catalogue.LedgerOperation{{Name: "title", Effect: catalogue.EffectSetFields, Fields: []string{"title"}, Author: true}}}}, nil
|
||||
},
|
||||
writer: func(string) (*bus.Conn, error) { return conn, nil },
|
||||
}}
|
||||
return all.forModule("m").(*moduleBus), all.forModule("other").(*moduleBus), mesh
|
||||
@@ -138,6 +138,7 @@ func TestAnApplyIsStampedWithTheCallerTheRuntimeProved(t *testing.T) {
|
||||
{"1966/AC17 a call in flight stamps its proven caller", `{"ledger":"records","instance":"decision/1","operation":"title",` + forged + `,"call":"` + call + `","author":"forged"}`, "op@shanks", ""},
|
||||
{"naming no call stamps the account alone", `{"ledger":"records","instance":"decision/2","operation":"title",` + forged + `}`, "", ""},
|
||||
{"1966/AC18 another module's call is refused, naming it", `{"ledger":"records","instance":"decision/3","operation":"title",` + forged + `,"call":"` + theirs + `"}`, "", "the call " + theirs + ", which is another module's"},
|
||||
{"a ledger bound with no types is refused, naming it", `{"ledger":"untyped","instance":"decision/3","operation":"title",` + forged + `}`, "", `m's ledger "untyped" is bound with no types`},
|
||||
{"a call not in flight is refused, naming it", `{"ledger":"records","instance":"decision/3","operation":"title",` + forged + `,"call":"c0ffee"}`, "", "the call c0ffee, which is not a tool call in flight"},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
@@ -242,11 +242,6 @@ func WithLedgerWriters(writer func(module string) (*bus.Conn, error)) Option {
|
||||
return func(c *consumers) { c.ledgers.writer = writer }
|
||||
}
|
||||
|
||||
// WithLedgerTypes gives the runtime where it reads a ledger's types (ledgers.go).
|
||||
func WithLedgerTypes(types LedgerTypes) Option {
|
||||
return func(c *consumers) { c.ledgers.types = types }
|
||||
}
|
||||
|
||||
// Run launches, binds and serves, and serves each module's bundle again when it appears, changes or
|
||||
// goes, with no restart. It answers a stop function.
|
||||
func Run(conn *bus.Conn, served []Served, envs map[string]map[string]string, logf func(string, ...any), opts ...Option) (func(), error) {
|
||||
|
||||
Reference in New Issue
Block a user