D1 (view): the view's tools ask the indexer, the assigner and the wants' bucket of the holders of their seats
The tools program asks indexer_read_*, assigner_read_* and indexer_pending of the module holding the seat mesh-catalog or mesh-wants when it asks, and names the wants' real bucket from that holder at each status; the assigner's module constant goes. Builds: design 47 G §428, §717, A-210, A-300, A-318 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -398,14 +398,30 @@ func read(state stdio.KeptState) view.StateRead {
|
||||
// ownersRead is the view's reading of the indexer's and the assigner's states: each through its owner's read tool, asked
|
||||
// by the bundle on each call (ADR 0615 §1, design 25 § 12.2: the view keeps its tools and answers them by asking the
|
||||
// owners), never their buckets.
|
||||
func ownersRead(ask func(key string, body any) (json.RawMessage, error), catalog *meshcatalogclient.Branches) view.Reader {
|
||||
// Each owner's read tool as the view's invokes names it.
|
||||
indexer := func(tool string) view.StateRead { return view.OwnerRead(ask, view.IndexerModule+"."+tool) }
|
||||
assigner := func(tool string) view.StateRead { return view.OwnerRead(ask, view.AssignerModule+"."+tool) }
|
||||
func ownersRead(ask func(key string, body any) (json.RawMessage, error), holder func(seat string) (string, bool), catalog *meshcatalogclient.Branches) view.Reader {
|
||||
// Each owner's read tool as the view's invokes names it, asked of the module that holds the owner's seat when it is asked
|
||||
// (design 47 G A-210, §428): the indexer holds the seat mesh-catalog, the assigner the seat mesh-wants.
|
||||
atHolder := func(seat string) func(key string, body any) (json.RawMessage, error) {
|
||||
return func(key string, body any) (json.RawMessage, error) {
|
||||
module, ok := holder(seat)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the holder of %s is not found, so %s is not asked", seat, key)
|
||||
}
|
||||
return ask(module+"."+key, body)
|
||||
}
|
||||
}
|
||||
indexer := func(tool string) view.StateRead { return view.OwnerRead(atHolder(view.CatalogSeat), tool) }
|
||||
assigner := func(tool string) view.StateRead { return view.OwnerRead(atHolder(meshwants.Seat), tool) }
|
||||
return view.Reader{State: catalogRead(catalog), CandidatesState: indexer("indexer_read_candidates"),
|
||||
WantsState: assigner("assigner_read_wants"), DifferencesState: assigner("assigner_read_differences"),
|
||||
// The wants' real bucket at the module the tools program reads them of (A-300), as the watchman's stuck keys name it.
|
||||
WantsHeld: view.AssignerModule + "_" + meshwants.StateWants,
|
||||
WantsHeld: func() string {
|
||||
module, ok := holder(meshwants.Seat)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return module + "_" + meshwants.StateWants
|
||||
},
|
||||
HeardState: indexer("indexer_read_heard"),
|
||||
PredefinedState: indexer("indexer_read_predefined"),
|
||||
ConfigCandidatesState: indexer("indexer_read_config_candidates"),
|
||||
@@ -449,7 +465,7 @@ func main() {
|
||||
}
|
||||
branches.Logf = logf
|
||||
cat.Start(context.Background())
|
||||
r := ownersRead(stdio.Ask, branches)
|
||||
r := ownersRead(stdio.Ask, holderOf(stdio.Ask), branches)
|
||||
r.Builds, r.HavesState = read(stdio.State("mesh-delivery-builder.builds")), read(stdio.State("node-engine.have"))
|
||||
r.ReachState = read(stdio.State("reach"))
|
||||
r.WaitsState = read(stdio.State(viewstate.WaitsState))
|
||||
@@ -460,7 +476,7 @@ func main() {
|
||||
r.Assignment, r.Difference = view.AssignmentOf(stdio.Ask), view.DifferenceOf(stdio.Ask)
|
||||
r.ProvisionsState = read(stdio.State(view.ProvisionsRead))
|
||||
r.BuildLog = stdio.Log(builds.BuildLogRead).Since
|
||||
r.Pending = pendingOf
|
||||
r.Pending = pendingOf(holderOf(stdio.Ask))
|
||||
// The build modules are no names (design 47 G A-82): the build seats on the seat list, each held by the module the seat
|
||||
// list names for it; the seat list and its two tools are wired once by mesh-sdk (§204).
|
||||
// Its callers name seats, never a module, resolved from the seat-holders view the catalog half's claimant serves (A-295, A-298).
|
||||
@@ -501,11 +517,18 @@ func AnswersTool(callers stdio.Callers, state func(string) views.Source, now fun
|
||||
callers, state(viewstate.ViewAnswersState), state(views.MarksState), now)
|
||||
}
|
||||
|
||||
// pendingOf asks the indexer's tool indexer_pending (declared in invokes): what it holds back and why (ADR 0633 §5).
|
||||
func pendingOf() (takein.PendingAnswer, error) {
|
||||
raw, err := stdio.Ask(view.IndexerModule+".indexer_pending", map[string]any{})
|
||||
if err != nil {
|
||||
return takein.PendingAnswer{}, err
|
||||
// pendingOf asks the indexer's tool indexer_pending (declared in invokes) of the module holding the seat mesh-catalog: what it
|
||||
// holds back and why (ADR 0633 §5; design 47 G A-210).
|
||||
func pendingOf(holder func(seat string) (string, bool)) func() (takein.PendingAnswer, error) {
|
||||
return func() (takein.PendingAnswer, error) {
|
||||
module, ok := holder(view.CatalogSeat)
|
||||
if !ok {
|
||||
return takein.PendingAnswer{}, fmt.Errorf("the holder of %s is not found", view.CatalogSeat)
|
||||
}
|
||||
raw, err := stdio.Ask(module+".indexer_pending", map[string]any{})
|
||||
if err != nil {
|
||||
return takein.PendingAnswer{}, err
|
||||
}
|
||||
return takein.DecodePendingAnswer(raw)
|
||||
}
|
||||
return takein.DecodePendingAnswer(raw)
|
||||
}
|
||||
|
||||
@@ -13,11 +13,27 @@ import (
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/graph"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// assignerModule is the module the tests run the assigner as, as the seat list names it; no code of the view names it.
|
||||
const assignerModule = "mesh-delivery-assigner"
|
||||
|
||||
// holders answers the seat list as a mesh whose indexer and assigner are not the modules the view once named: the module that
|
||||
// holds the seat mesh-catalog and the one that holds mesh-wants, each under a name no code of the view knows (design 47 G A-210).
|
||||
func holders(seat string) (string, bool) {
|
||||
switch seat {
|
||||
case meshcatalog.Seat:
|
||||
return "moved-" + view.IndexerModule, true
|
||||
case meshwants.Seat:
|
||||
return "moved-" + assignerModule, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// source is a fake owner state as a views.Source: what the owner's read tool answers from.
|
||||
type source map[string]string
|
||||
|
||||
@@ -57,9 +73,9 @@ func ownersAnswerTheToolsAsBefore(t *testing.T) {
|
||||
}
|
||||
states := map[string]source{
|
||||
view.IndexerModule + ".candidates": {"feat/x": cand, "fix": `{"v":1,"group":"fix","base":3,"heads":"h","pulls":[{"modules":["netcheck"]}]}`},
|
||||
view.AssignerModule + ".wants": {"want.ace.zsh": `{"fp":"` + fp2A + `"}`, "want.novox.zsh": `{"fp":"` + fp2B + `"}`,
|
||||
assignerModule + ".wants": {"want.ace.zsh": `{"fp":"` + fp2A + `"}`, "want.novox.zsh": `{"fp":"` + fp2B + `"}`,
|
||||
"held." + keyB: `{"node":"ace","words":"failed"}`},
|
||||
view.AssignerModule + ".differences": {"ace.zsh": `{"unexplained":true}`, "novox.zsh": `{"rule":3}`},
|
||||
assignerModule + ".differences": {"ace.zsh": `{"unexplained":true}`, "novox.zsh": `{"rule":3}`},
|
||||
view.IndexerModule + "." + view.IndexerPredefinedState: {view.PredefinedListKey: `{"v":1,"keys":["dir:state"]}`},
|
||||
view.IndexerModule + "." + view.IndexerConfigCandidatesState: {"novox.r.7": `{"v":1,"pull":"novox/r#7"}`},
|
||||
view.IndexerModule + "." + view.PackagesState: {pkgKey: `{"v":1,"module":"git.novox.be/novox/mesh-sdk/go","commit":"c1"}`},
|
||||
@@ -71,13 +87,13 @@ func ownersAnswerTheToolsAsBefore(t *testing.T) {
|
||||
const heard1 = `{"v":1,"subject":"s","sequence":41,"repository":"novox/mesh-catalog","commit":"9cdac09a"}`
|
||||
states[view.IndexerModule+".heard"] = source{"forge": heard1}
|
||||
histories := map[string]map[string][]views.Revision{
|
||||
view.IndexerModule + ".heard": {"forge": {{Revision: 3, At: at, Op: "put", Value: json.RawMessage(heard1)}}},
|
||||
view.AssignerModule + ".wants": {"want.ace.zsh": {{Revision: 12, At: at, Op: "put", Value: json.RawMessage(`{"fp":"` + fp2A + `"}`)}}},
|
||||
view.IndexerModule + ".heard": {"forge": {{Revision: 3, At: at, Op: "put", Value: json.RawMessage(heard1)}}},
|
||||
assignerModule + ".wants": {"want.ace.zsh": {{Revision: 12, At: at, Op: "put", Value: json.RawMessage(`{"fp":"` + fp2A + `"}`)}}},
|
||||
}
|
||||
// The direct reading, as the tools read the states before (the reads the manifest no longer has).
|
||||
st := func(name string) view.StateRead { return fake(states[name]).reader().State }
|
||||
direct := view.Reader{CandidatesState: st(view.IndexerModule + ".candidates"),
|
||||
WantsState: st(view.AssignerModule + ".wants"), DifferencesState: st(view.AssignerModule + ".differences"),
|
||||
WantsState: st(assignerModule + ".wants"), DifferencesState: st(assignerModule + ".differences"),
|
||||
PredefinedState: st(view.IndexerModule + "." + view.IndexerPredefinedState),
|
||||
ConfigCandidatesState: st(view.IndexerModule + "." + view.IndexerConfigCandidatesState),
|
||||
PackagesState: st(view.IndexerModule + "." + view.PackagesState),
|
||||
@@ -86,7 +102,7 @@ func ownersAnswerTheToolsAsBefore(t *testing.T) {
|
||||
tools := map[string]string{}
|
||||
for name := range states {
|
||||
owner, state, _ := strings.Cut(name, ".")
|
||||
short := map[string]string{view.IndexerModule: "indexer_read_", view.AssignerModule: "assigner_read_"}[owner]
|
||||
short := map[string]string{view.IndexerModule: "indexer_read_", assignerModule: "assigner_read_"}[owner]
|
||||
tools[owner+"."+short+strings.ReplaceAll(state, "-", "_")] = name
|
||||
}
|
||||
asked := map[string]int{}
|
||||
@@ -95,6 +111,11 @@ func ownersAnswerTheToolsAsBefore(t *testing.T) {
|
||||
asked[key]++
|
||||
return a()
|
||||
}
|
||||
// The owners are asked at the holders the seat list names, never at the modules the view once named.
|
||||
if strings.HasPrefix(key, view.IndexerModule+".") || strings.HasPrefix(key, assignerModule+".") {
|
||||
return nil, fmt.Errorf("%s is asked of a module named in code, not of the seat's holder", key)
|
||||
}
|
||||
key = strings.TrimPrefix(key, "moved-")
|
||||
name, ok := tools[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no tool %s", key)
|
||||
@@ -119,7 +140,7 @@ func ownersAnswerTheToolsAsBefore(t *testing.T) {
|
||||
}
|
||||
return json.Marshal(a)
|
||||
}
|
||||
owned := ownersRead(ask, startedBranches(t, context.Background(), ask))
|
||||
owned := ownersRead(ask, holders, startedBranches(t, context.Background(), ask))
|
||||
for _, row := range []struct {
|
||||
tool string
|
||||
args map[string]any
|
||||
@@ -174,7 +195,7 @@ func ownersAnswerTheToolsAsBefore(t *testing.T) {
|
||||
away := func(key string, body any) (json.RawMessage, error) { return nil, fmt.Errorf("the holder is away") }
|
||||
gone, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
none := ownersRead(away, startedBranches(t, gone, away))
|
||||
none := ownersRead(away, holders, startedBranches(t, gone, away))
|
||||
if out, err := run(t, none, view.ModulesReadTool, map[string]any{}); err == nil || !strings.Contains(err.Error(), "no watched branch") {
|
||||
t.Errorf("indexer_modules with no view = %v, %v, want an error naming it", out, err)
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ import (
|
||||
// `consumes`); a unit row checks them against it (TestTheNamesAreTheManifests). No contract names a holder (novox/hq
|
||||
// ADR 0625 §4, ADR 0634 §1). The indexer's module and its reads are the catalog client's.
|
||||
const (
|
||||
IndexerModule = "mesh-delivery-indexer"
|
||||
AssignerModule = "mesh-delivery-assigner"
|
||||
IndexerModule = "mesh-delivery-indexer"
|
||||
// ProvisionsRead is the seat mesh-provisioner's state provisions, as the manifest's `reads` names it.
|
||||
ProvisionsRead = "mesh-provisioner.provisions"
|
||||
WatchmanModule = "mesh-delivery-watchman"
|
||||
|
||||
@@ -51,13 +51,13 @@ type Reader struct {
|
||||
State StateRead // the indexer's modules
|
||||
Builds StateRead // the builder's builds
|
||||
|
||||
CandidatesState StateRead // the indexer's candidates
|
||||
WantsState StateRead // the assigner's wants and holds
|
||||
WantsHeld string // the wants' real bucket at the module WantsState reads, which the watchman's stuck keys name
|
||||
DifferencesState StateRead // the assigner's differences
|
||||
HavesState StateRead // the node-engine's haves
|
||||
ReachState StateRead // the view's own reach (ADR 0556)
|
||||
WaitsState StateRead // the view's own waits: the parts' wait-held (design 47 G §512, A-242)
|
||||
CandidatesState StateRead // the indexer's candidates
|
||||
WantsState StateRead // the assigner's wants and holds
|
||||
WantsHeld func() string // the wants' real bucket at the module WantsState reads, read at each use, which the watchman's stuck keys name
|
||||
DifferencesState StateRead // the assigner's differences
|
||||
HavesState StateRead // the node-engine's haves
|
||||
ReachState StateRead // the view's own reach (ADR 0556)
|
||||
WaitsState StateRead // the view's own waits: the parts' wait-held (design 47 G §512, A-242)
|
||||
// HeardState is the FORGE messages the indexer handled (issue 1126 note 76); unset reads none.
|
||||
HeardState StateRead
|
||||
// SignersState is the vault's public signing halves (ADR 0550 §2); unset reads none.
|
||||
|
||||
@@ -42,9 +42,9 @@ const HealsWithin = 7 * 24 * time.Hour
|
||||
// StatusRead is the five buckets status reads, as a tool reads them.
|
||||
type StatusRead struct {
|
||||
Conditions, Attempts, Presence, Stuck, Wants, Haves StateRead
|
||||
// WantsHeld is the real bucket of the wants at their holder, which the watchman's stuck keys name (design 47 G A-303);
|
||||
// "" reads no want as stuck.
|
||||
WantsHeld string
|
||||
// WantsHeld answers the real bucket of the wants at their holder, which the watchman's stuck keys name (design 47 G A-303),
|
||||
// read at each status so a moved holder is named as it moves; "" (or none) reads no want as stuck.
|
||||
WantsHeld func() string
|
||||
}
|
||||
|
||||
// Status is the view's answer to status.
|
||||
@@ -157,6 +157,10 @@ func DeriveStatus(r StatusRead, now time.Time) Status {
|
||||
|
||||
wants, werr := values(r.Wants)
|
||||
haves, herr := values(r.Haves)
|
||||
held := ""
|
||||
if r.WantsHeld != nil {
|
||||
held = r.WantsHeld()
|
||||
}
|
||||
switch {
|
||||
case werr != nil:
|
||||
st.MovingUnread = "the assigner's wants cannot be read: " + werr.Error()
|
||||
@@ -181,7 +185,7 @@ func DeriveStatus(r StatusRead, now time.Time) Status {
|
||||
}
|
||||
}
|
||||
words, stopped := "", false
|
||||
if sk, err := meshwatchdogsclient.StuckKey(r.WantsHeld, wk); err == nil && serr == nil && r.WantsHeld != "" {
|
||||
if sk, err := meshwatchdogsclient.StuckKey(held, wk); err == nil && serr == nil && held != "" {
|
||||
if s, err := meshwatchdogsclient.DecodeStuck(stuck[sk]); err == nil && !s.Moved {
|
||||
words, stopped = s.Waited, true
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ func statusDerivesEachPartFromItsBucket(t *testing.T) {
|
||||
"core.doctor.self-check": enc(mcseat.ConditionsByKeyValue{Key: "core.doctor.self-check", Severity: mcc.Urgent, Summary: "no run", Raised: t0.Add(time.Minute)}),
|
||||
}),
|
||||
Presence: states(map[string]string{"a": presence(true), "b": presence(false)}),
|
||||
WantsHeld: held,
|
||||
WantsHeld: func() string { return held },
|
||||
Stuck: states(map[string]string{stuckWant: stuckOf(held, "want.b.web", "b to run web", false),
|
||||
"x.moved": stuckOf("x", "moved", "y", true)}),
|
||||
Wants: states(map[string]string{"want.a.web": want(fp2), "want.a.mail": want(fp1), "want.b.web": want(fp2), "want.a.db": want(fp3)}),
|
||||
|
||||
@@ -258,7 +258,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7
|
||||
t.Errorf("the view follows %s, its manifest consumes %v", family, m.Consumes)
|
||||
}
|
||||
}
|
||||
for _, tool := range []string{meshcatalogclient.RevisionVerb, meshcatalogclient.RegistrationsVerb, meshcatalogclient.SourceVerb, meshcatalogclient.EntryVerb, view.IndexerModule + ".indexer_read_candidates", view.IndexerModule + ".indexer_pending", view.AssignerModule + ".assigner_read_wants", view.TellerModule + ".teller_read_answers"} {
|
||||
for _, tool := range []string{meshcatalogclient.RevisionVerb, meshcatalogclient.RegistrationsVerb, meshcatalogclient.SourceVerb, meshcatalogclient.EntryVerb, view.IndexerModule + ".indexer_read_candidates", view.IndexerModule + ".indexer_pending", "mesh-delivery-assigner.assigner_read_wants", view.TellerModule + ".teller_read_answers"} {
|
||||
if !slices.Contains(verbs(m.Invokes), tool) {
|
||||
t.Errorf("the view asks %s, its manifest invokes %v", tool, m.Invokes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user