The assigner and the indexer's hold count a bootstrapped seat as read (issue 1966)
mesh/delivery merged: source.main moved to novox/mesh-delivery@7f412185525e
mesh/delivery-group group fix/1966-seat-reads-bootstrap merged: every member is merged
mesh/check failed (2): mesh-delivery-checker: no published of the tagged version of go-sdk of mesh-delivery…; and 1 more | ee6fd92b4d1a on 7f412185525e
mesh/delivery merged: source.main moved to novox/mesh-delivery@7f412185525e
mesh/delivery-group group fix/1966-seat-reads-bootstrap merged: every member is merged
mesh/check failed (2): mesh-delivery-checker: no published of the tagged version of go-sdk of mesh-delivery…; and 1 more | ee6fd92b4d1a on 7f412185525e
Every reader of the seat list holds the bootstrapped seats from its start (ADR 0636 §2), so a manifest naming one is read by every node-engine whatever its statement names. graph.MissingReads, in the Delivery domain's SDK, is the one rule both holds judge with; mesh-sdk is not changed. ace's engine states no seat, so the want of the node-engine's own build 0bd8cbad (needing mesh-catalog) and of restic (needing node-backup) were held on ace: the engine that would state them could not be delivered. The indexer's hold judges only mesh-wide reads today, so its verdicts do not change; it takes the rule so the two stay one. Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
@@ -186,14 +186,15 @@ func holds(m Module, node string, n Nodes, ordering bool) (bool, int, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readerLacking says why the module's want on node is held for the node's engine (ADR 0614 §4): a reader capability its
|
// readerLacking says why the module's want on node is held for the node's engine (ADR 0614 §4): a reader capability its
|
||||||
// manifest needs that the engine does not state, read as the baseline when it states none. It is held under rule 7 (the
|
// manifest needs that the engine does not state, read as the baseline when it states none, a bootstrapped seat counting as
|
||||||
|
// read by every engine (graph.MissingReads, ADR 0636 §2). It is held under rule 7 (the
|
||||||
// composition), since no rule of its own is numbered in this release, with words naming the capability; it moves once the
|
// composition), since no rule of its own is numbered in this release, with words naming the capability; it moves once the
|
||||||
// engine states it. The node-engine's own new build is held only by a word of its own manifest no engine states.
|
// engine states it. The node-engine's own new build is held only by a word of its own manifest no engine states.
|
||||||
//
|
//
|
||||||
// A build's reference forms hold the want only when the engine reads none of them (ADR 0624 §2): every engine reads a
|
// A build's reference forms hold the want only when the engine reads none of them (ADR 0624 §2): every engine reads a
|
||||||
// first form, so a build written in one is never held by its forms; the words name the forms it reads none of.
|
// first form, so a build written in one is never held by its forms; the words name the forms it reads none of.
|
||||||
func (m Module) readerLacking(node string, n Nodes) (string, bool) {
|
func (m Module) readerLacking(node string, n Nodes) (string, bool) {
|
||||||
missing := catalogue.MissingManifestReads(m.Needs, n.Reads[node])
|
missing := graph.MissingReads(m.Needs, n.Reads[node])
|
||||||
if len(missing) == 0 && forms.Current.Held(m.FormNeeds, n.Reads[node]) {
|
if len(missing) == 0 && forms.Current.Held(m.FormNeeds, n.Reads[node]) {
|
||||||
missing = slices.Clone(m.FormNeeds)
|
missing = slices.Clone(m.FormNeeds)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,15 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
|
|||||||
map[string][]string{"ace": catalogue.ManifestReads(), "g14": catalogue.ManifestReads()}, false, nil, ""},
|
map[string][]string{"ace": catalogue.ManifestReads(), "g14": catalogue.ManifestReads()}, false, nil, ""},
|
||||||
{"the baseline's words on engines stating nothing", "mail", []string{"word:claims.serves", "seat:mesh-catalog", "form:config"}, nil, false, nil, ""},
|
{"the baseline's words on engines stating nothing", "mail", []string{"word:claims.serves", "seat:mesh-catalog", "form:config"}, nil, false, nil, ""},
|
||||||
{"the node-engine's new build, whose contract has more words than its manifest uses", "mesh-host", hostNeeds, nil, false, nil, ""},
|
{"the node-engine's new build, whose contract has more words than its manifest uses", "mesh-host", hostNeeds, nil, false, nil, ""},
|
||||||
|
// Issue 1966: g14's engine states its words and forms but no seat (its statement made before its seat list was in
|
||||||
|
// use); needs of the bootstrapped seats mesh-catalog and node-backup move all the same (ADR 0636 §2), one of a seat
|
||||||
|
// not bootstrapped is held.
|
||||||
|
{"mesh-catalog on an engine stating no seat", "mesh-host", []string{"seat:mesh-catalog", "word:module"},
|
||||||
|
map[string][]string{"g14": {"word:module"}}, false, nil, ""},
|
||||||
|
{"node-backup on an engine stating no seat", "restic", []string{"seat:node-backup", "word:module"},
|
||||||
|
map[string][]string{"g14": {"word:module"}}, false, nil, ""},
|
||||||
|
{"a seat not bootstrapped on an engine stating no seat: held, naming it", "mail", []string{"seat:mesh-vault", "word:module"},
|
||||||
|
map[string][]string{"g14": {"word:module"}}, true, nil, "seat:mesh-vault"},
|
||||||
// ADR 0624 §2, with form:new a form no engine reads unless it states it and artifact-store the first form.
|
// ADR 0624 §2, with form:new a form no engine reads unless it states it and artifact-store the first form.
|
||||||
{"g14 back stating {old}, a build carrying {old,new}: moves", "mail", nil, map[string][]string{"g14": {"word:module"}}, false,
|
{"g14 back stating {old}, a build carrying {old,new}: moves", "mail", nil, map[string][]string{"g14": {"word:module"}}, false,
|
||||||
[]string{"form:artifact-store", "form:new"}, ""},
|
[]string{"form:artifact-store", "form:new"}, ""},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
|
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
@@ -129,6 +130,7 @@ func meshWide(needs []string) []string {
|
|||||||
const noneCounted = "no node is counted yet (the assignments is not read), so the hold stays until it is"
|
const noneCounted = "no node is counted yet (the assignments is not read), so the hold stays until it is"
|
||||||
|
|
||||||
// lacking is, of the counted nodes, each whose engine lacks some of needs, with what it lacks; or why no node is counted.
|
// lacking is, of the counted nodes, each whose engine lacks some of needs, with what it lacks; or why no node is counted.
|
||||||
|
// It judges as the assigner does (graph.MissingReads): a bootstrapped seat is read by every engine.
|
||||||
func (ix *Indexer) lacking(needs []string) (map[string][]string, string) {
|
func (ix *Indexer) lacking(needs []string) (map[string][]string, string) {
|
||||||
counted, known := ix.Counted()
|
counted, known := ix.Counted()
|
||||||
if !known || len(counted) == 0 {
|
if !known || len(counted) == 0 {
|
||||||
@@ -136,7 +138,7 @@ func (ix *Indexer) lacking(needs []string) (map[string][]string, string) {
|
|||||||
}
|
}
|
||||||
out := map[string][]string{}
|
out := map[string][]string{}
|
||||||
for node, stated := range counted {
|
for node, stated := range counted {
|
||||||
if miss := catalogue.MissingManifestReads(needs, stated); len(miss) > 0 {
|
if miss := graph.MissingReads(needs, stated); len(miss) > 0 {
|
||||||
out[node] = miss
|
out[node] = miss
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,4 +112,10 @@ func storeHoldersNewManifestWaitsForEveryCountedEngine(t *testing.T) {
|
|||||||
t.Fatalf("once every counted engine states form:seat-reach modules.distribution = manifest %s commit %s, %d holds, finding %s, bounds %v; want the new entry and none",
|
t.Fatalf("once every counted engine states form:seat-reach modules.distribution = manifest %s commit %s, %d holds, finding %s, bounds %v; want the new entry and none",
|
||||||
m.Manifest, m.Commit, len(holds.vals), findings.vals[heldKey], bounds)
|
m.Manifest, m.Commit, len(holds.vals), findings.vals[heldKey], bounds)
|
||||||
}
|
}
|
||||||
|
// Issue 1966: the hold judges as the assigner does (graph.MissingReads): a bootstrapped seat is read by every engine,
|
||||||
|
// so one whose statement names no seat lacks none of it (ADR 0636 §2).
|
||||||
|
stated = map[string][]string{"n1": {"word:module"}}
|
||||||
|
if l, why := ix.lacking([]string{"seat:mesh-catalog", "form:seat-reach"}); why != "" || len(l) != 1 || !slices.Equal(l["n1"], []string{"form:seat-reach"}) {
|
||||||
|
t.Fatalf("an engine stating no seat lacks %v (%s), want form:seat-reach alone", l, why)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,25 @@ func TestGraphEdges(t *testing.T) {
|
|||||||
t.Run("a node's value", nodeValues)
|
t.Run("a node's value", nodeValues)
|
||||||
t.Run("a reader of the seat list waits for its holder", readersOfTheSeatList)
|
t.Run("a reader of the seat list waits for its holder", readersOfTheSeatList)
|
||||||
t.Run("a verb is served by a running healthy have of the node-engine contract", servedByHaves)
|
t.Run("a verb is served by a running healthy have of the node-engine contract", servedByHaves)
|
||||||
|
// ADR 0636 §2, issue 1966: a need of a bootstrapped seat is read by every engine whatever its statement names; a seat
|
||||||
|
// not bootstrapped and a word stay missing, and an engine stating none is read as the baseline.
|
||||||
|
t.Run("a bootstrapped seat is read by every engine", func(t *testing.T) {
|
||||||
|
stated := []string{"word:claims", "word:invokes"}
|
||||||
|
for _, c := range []struct {
|
||||||
|
name string
|
||||||
|
needs, stated []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"mesh-catalog and node-backup", []string{"seat:mesh-catalog", "seat:node-backup", "word:invokes"}, stated, nil},
|
||||||
|
{"a seat not bootstrapped", []string{"seat:mesh-catalog", "seat:mesh-vault"}, stated, []string{"seat:mesh-vault"}},
|
||||||
|
{"a word", []string{"seat:node-engine", "word:zone"}, stated, []string{"word:zone"}},
|
||||||
|
{"the baseline", []string{"seat:mesh-catalog", "form:seat-reach"}, nil, []string{"form:seat-reach"}},
|
||||||
|
} {
|
||||||
|
if got := MissingReads(c.needs, c.stated); !slices.Equal(got, c.want) {
|
||||||
|
t.Errorf("%s: missing %v, want %v", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
fp := "src2:" + strings.Repeat("b", 64)
|
fp := "src2:" + strings.Repeat("b", 64)
|
||||||
node := func(repository, path string, edit func(*Node)) Node {
|
node := func(repository, path string, edit func(*Node)) Node {
|
||||||
n := Node{FP: fp, Repository: repository, Path: path}
|
n := Node{FP: fp, Repository: repository, Path: path}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bootstrappedReads are the reader capabilities `seat:<seat>` of the bootstrapped seats: the embedded files' own, so
|
||||||
|
// computed once.
|
||||||
|
var bootstrappedReads = sync.OnceValue(func() []string {
|
||||||
|
var out []string
|
||||||
|
for _, d := range seatlist.Bootstrapped() {
|
||||||
|
out = append(out, catalogue.ReadSeat+d.Seat)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
})
|
||||||
|
|
||||||
|
// MissingReads is catalogue.MissingManifestReads with every need of a bootstrapped seat counted as read (novox/hq ADR 0614
|
||||||
|
// §4, ADR 0636 §2): every reader of the seat list holds the bootstrapped seats from its start, before it has read the list,
|
||||||
|
// so a manifest naming one is read by every node-engine whatever its statement names. Without it an engine whose statement
|
||||||
|
// was made before its list was in use states no seat, and the node-engine's own build naming mesh-catalog is held on the
|
||||||
|
// very engine that would state it (issue 1966). The assigner's capability hold and the indexer's hold both judge with it,
|
||||||
|
// so the rule is one.
|
||||||
|
func MissingReads(needs, stated []string) []string {
|
||||||
|
boot := bootstrappedReads()
|
||||||
|
missing := slices.DeleteFunc(catalogue.MissingManifestReads(needs, stated), func(n string) bool {
|
||||||
|
return strings.HasPrefix(n, catalogue.ReadSeat) && slices.Contains(boot, n)
|
||||||
|
})
|
||||||
|
if len(missing) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return missing
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user